For pseries machine emulation, in order to move the interrupt
controller code to the kernel, we need to intercept some RTAS
calls in the kernel itself

Signed-off-by: Michael Ellerman <[email protected]>
Signed-off-by: Benjamin Herrenschmidt <[email protected]>
---
 arch/powerpc/include/asm/hvcall.h   |    3 +
 arch/powerpc/include/asm/kvm.h      |    6 ++
 arch/powerpc/include/asm/kvm_host.h |    1 +
 arch/powerpc/include/asm/kvm_ppc.h  |    4 +
 arch/powerpc/kvm/Makefile           |    1 +
 arch/powerpc/kvm/book3s_hv.c        |   17 ++++
 arch/powerpc/kvm/book3s_pr_papr.c   |    8 ++
 arch/powerpc/kvm/book3s_rtas.c      |  182 +++++++++++++++++++++++++++++++++++
 arch/powerpc/kvm/powerpc.c          |    9 +-
 include/linux/kvm.h                 |    3 +
 10 files changed, 232 insertions(+), 2 deletions(-)
 create mode 100644 arch/powerpc/kvm/book3s_rtas.c

diff --git a/arch/powerpc/include/asm/hvcall.h 
b/arch/powerpc/include/asm/hvcall.h
index 423cf9e..f46c0a0 100644
--- a/arch/powerpc/include/asm/hvcall.h
+++ b/arch/powerpc/include/asm/hvcall.h
@@ -274,6 +274,9 @@
 #define H_GET_MPP_X            0x314
 #define MAX_HCALL_OPCODE       H_GET_MPP_X
 
+/* Platform specific hcalls, used by KVM */
+#define H_RTAS                 0xf000
+
 #ifndef __ASSEMBLY__
 
 /**
diff --git a/arch/powerpc/include/asm/kvm.h b/arch/powerpc/include/asm/kvm.h
index 1bea4d8..3dc91df 100644
--- a/arch/powerpc/include/asm/kvm.h
+++ b/arch/powerpc/include/asm/kvm.h
@@ -290,6 +290,12 @@ struct kvm_allocate_rma {
        __u64 rma_size;
 };
 
+/* for KVM_CAP_PPC_RTAS */
+struct kvm_rtas_token_args {
+       char name[120];
+       __u64 token;    /* Use a token of 0 to undefine a mapping */
+};
+
 struct kvm_book3e_206_tlb_entry {
        __u32 mas8;
        __u32 mas1;
diff --git a/arch/powerpc/include/asm/kvm_host.h 
b/arch/powerpc/include/asm/kvm_host.h
index 50ea12f..d9c3f63 100644
--- a/arch/powerpc/include/asm/kvm_host.h
+++ b/arch/powerpc/include/asm/kvm_host.h
@@ -250,6 +250,7 @@ struct kvm_arch {
 #endif /* CONFIG_KVM_BOOK3S_64_HV */
 #ifdef CONFIG_PPC_BOOK3S_64
        struct list_head spapr_tce_tables;
+       struct list_head rtas_tokens;
 #endif
 };
 
diff --git a/arch/powerpc/include/asm/kvm_ppc.h 
b/arch/powerpc/include/asm/kvm_ppc.h
index a288bec..e23bfc6 100644
--- a/arch/powerpc/include/asm/kvm_ppc.h
+++ b/arch/powerpc/include/asm/kvm_ppc.h
@@ -149,6 +149,10 @@ extern int kvm_vm_ioctl_get_smmu_info(struct kvm *kvm,
 extern int kvmppc_bookehv_init(void);
 extern void kvmppc_bookehv_exit(void);
 
+extern int kvm_vm_ioctl_rtas_define_token(struct kvm *kvm, void __user *argp);
+extern int kvmppc_rtas_hcall(struct kvm_vcpu *vcpu);
+extern void kvmppc_rtas_tokens_free(struct kvm *kvm);
+
 /*
  * Cuts out inst bits with ordering according to spec.
  * That means the leftmost bit is zero. All given bits are included.
diff --git a/arch/powerpc/kvm/Makefile b/arch/powerpc/kvm/Makefile
index c2a0863..536f65f 100644
--- a/arch/powerpc/kvm/Makefile
+++ b/arch/powerpc/kvm/Makefile
@@ -80,6 +80,7 @@ kvm-book3s_64-module-objs := \
        emulate.o \
        book3s.o \
        book3s_64_vio.o \
+       book3s_rtas.o \
        $(kvm-book3s_64-objs-y)
 
 kvm-objs-$(CONFIG_KVM_BOOK3S_64) := $(kvm-book3s_64-module-objs)
diff --git a/arch/powerpc/kvm/book3s_hv.c b/arch/powerpc/kvm/book3s_hv.c
index 257e05b..6199063 100644
--- a/arch/powerpc/kvm/book3s_hv.c
+++ b/arch/powerpc/kvm/book3s_hv.c
@@ -418,6 +418,7 @@ int kvmppc_pseries_do_hcall(struct kvm_vcpu *vcpu)
        unsigned long req = kvmppc_get_gpr(vcpu, 3);
        unsigned long target, ret = H_SUCCESS;
        struct kvm_vcpu *tvcpu;
+       int rc;
 
        switch (req) {
        case H_ENTER:
@@ -451,6 +452,19 @@ int kvmppc_pseries_do_hcall(struct kvm_vcpu *vcpu)
                                        kvmppc_get_gpr(vcpu, 5),
                                        kvmppc_get_gpr(vcpu, 6));
                break;
+       case H_RTAS:
+               if (list_empty(&vcpu->kvm->arch.rtas_tokens))
+                       return RESUME_HOST;
+
+               rc = kvmppc_rtas_hcall(vcpu);
+
+               if (rc == -ENOENT)
+                       return RESUME_HOST;
+               else if (rc == 0)
+                       break;
+
+               /* Send the error out to userspace via KVM_RUN */
+               return rc;
        default:
                return RESUME_HOST;
        }
@@ -1543,6 +1557,7 @@ int kvmppc_core_init_vm(struct kvm *kvm)
        kvm->arch.lpid = lpid;
 
        INIT_LIST_HEAD(&kvm->arch.spapr_tce_tables);
+       INIT_LIST_HEAD(&kvm->arch.rtas_tokens);
 
        kvm->arch.rma = NULL;
 
@@ -1585,6 +1600,8 @@ void kvmppc_core_destroy_vm(struct kvm *kvm)
                kvm->arch.rma = NULL;
        }
 
+       kvmppc_rtas_tokens_free(kvm);
+
        kvmppc_free_hpt(kvm);
        WARN_ON(!list_empty(&kvm->arch.spapr_tce_tables));
 }
diff --git a/arch/powerpc/kvm/book3s_pr_papr.c 
b/arch/powerpc/kvm/book3s_pr_papr.c
index ee02b30..175404a 100644
--- a/arch/powerpc/kvm/book3s_pr_papr.c
+++ b/arch/powerpc/kvm/book3s_pr_papr.c
@@ -246,6 +246,14 @@ int kvmppc_h_pr(struct kvm_vcpu *vcpu, unsigned long cmd)
                clear_bit(KVM_REQ_UNHALT, &vcpu->requests);
                vcpu->stat.halt_wakeup++;
                return EMULATE_DONE;
+       case H_RTAS:
+               if (list_empty(&vcpu->kvm->arch.rtas_tokens))
+                       return RESUME_HOST;
+               rc = kvmppc_rtas_hcall(vcpu);
+               if (rc != 0)
+                       break;
+               kvmppc_set_gpr(vcpu, 3, 0);
+               return EMULATE_DONE;
        }
 
        return EMULATE_FAIL;
diff --git a/arch/powerpc/kvm/book3s_rtas.c b/arch/powerpc/kvm/book3s_rtas.c
new file mode 100644
index 0000000..8a324e8
--- /dev/null
+++ b/arch/powerpc/kvm/book3s_rtas.c
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2012 Michael Ellerman, IBM Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2, as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/kvm_host.h>
+#include <linux/kvm.h>
+#include <linux/err.h>
+
+#include <asm/uaccess.h>
+#include <asm/kvm_book3s.h>
+#include <asm/kvm_ppc.h>
+#include <asm/hvcall.h>
+#include <asm/rtas.h>
+
+
+struct rtas_handler {
+       void (*handler)(struct kvm_vcpu *vcpu, struct rtas_args *args);
+       char *name;
+};
+
+static struct rtas_handler rtas_handlers[] = { };
+
+struct rtas_token_definition {
+       struct list_head list;
+       struct rtas_handler *handler;
+       u64 token;
+};
+
+static int rtas_name_matches(char *s1, char *s2)
+{
+       struct kvm_rtas_token_args args;
+       return !strncmp(s1, s2, sizeof(args.name));
+}
+
+static int rtas_token_undefine(struct kvm *kvm, char *name)
+{
+       struct rtas_token_definition *d, *tmp;
+
+       lockdep_assert_held(&kvm->lock);
+
+       list_for_each_entry_safe(d, tmp, &kvm->arch.rtas_tokens, list) {
+               if (rtas_name_matches(d->handler->name, name)) {
+                       list_del(&d->list);
+                       kfree(d);
+                       return 0;
+               }
+       }
+
+       /* It's not an error to undefine an undefined token */
+       return 0;
+}
+
+static int rtas_token_define(struct kvm *kvm, char *name, u64 token)
+{
+       struct rtas_token_definition *d;
+       struct rtas_handler *h;
+       bool found;
+       int i;
+
+       lockdep_assert_held(&kvm->lock);
+
+       list_for_each_entry(d, &kvm->arch.rtas_tokens, list) {
+               if (d->token == token)
+                       return -EEXIST;
+       }
+
+       found = false;
+       for (i = 0; i < ARRAY_SIZE(rtas_handlers); i++) {
+               h = &rtas_handlers[i];
+               if (rtas_name_matches(h->name, name)) {
+                       found = true;
+                       break;
+               }
+       }
+
+       if (!found)
+               return -ENOENT;
+
+       d = kzalloc(sizeof(*d), GFP_KERNEL);
+       if (!d)
+               return -ENOMEM;
+
+       d->handler = h;
+       d->token = token;
+
+       list_add_tail(&d->list, &kvm->arch.rtas_tokens);
+
+       return 0;
+}
+
+int kvm_vm_ioctl_rtas_define_token(struct kvm *kvm, void __user *argp)
+{
+       struct kvm_rtas_token_args args;
+       int rc;
+
+       if (copy_from_user(&args, argp, sizeof(args)))
+               return -EFAULT;
+
+       mutex_lock(&kvm->lock);
+
+       if (args.token)
+               rc = rtas_token_define(kvm, args.name, args.token);
+       else
+               rc = rtas_token_undefine(kvm, args.name);
+
+       mutex_unlock(&kvm->lock);
+
+       return rc;
+}
+
+int kvmppc_rtas_hcall(struct kvm_vcpu *vcpu)
+{
+       struct rtas_token_definition *d;
+       struct rtas_args args;
+       rtas_arg_t *orig_rets;
+       gpa_t args_phys;
+       int rc;
+
+       /* r4 contains the guest physical address of the RTAS args */
+       args_phys = kvmppc_get_gpr(vcpu, 4);
+
+       rc = kvm_read_guest(vcpu->kvm, args_phys, &args, sizeof(args));
+       if (rc)
+               goto fail;
+
+       /*
+        * args->rets is a pointer into args->args. Now that we've
+        * copied args we need to fix it up to point into our copy,
+        * not the guest args. We also need to save the original
+        * value so we can restore it on the way out.
+        */
+       orig_rets = args.rets;
+       args.rets = &args.args[args.nargs];
+
+       mutex_lock(&vcpu->kvm->lock);
+
+       rc = -ENOENT;
+       list_for_each_entry(d, &vcpu->kvm->arch.rtas_tokens, list) {
+               if (d->token == args.token) {
+                       d->handler->handler(vcpu, &args);
+                       rc = 0;
+                       break;
+               }
+       }
+
+       mutex_unlock(&vcpu->kvm->lock);
+
+       if (rc == 0) {
+               args.rets = orig_rets;
+               rc = kvm_write_guest(vcpu->kvm, args_phys, &args, sizeof(args));
+               if (rc)
+                       goto fail;
+       }
+
+       return rc;
+
+fail:
+       /*
+        * We only get here if the guest has called RTAS with a bogus
+        * args pointer. That means we can't get to the args, and so we
+        * can't fail the RTAS call. So fail right out to userspace,
+        * which should kill the guest.
+        */
+       return rc;
+}
+
+void kvmppc_rtas_tokens_free(struct kvm *kvm)
+{
+       struct rtas_token_definition *d, *tmp;
+
+       lockdep_assert_held(&kvm->lock);
+
+       list_for_each_entry_safe(d, tmp, &kvm->arch.rtas_tokens, list) {
+               list_del(&d->list);
+               kfree(d);
+       }
+}
diff --git a/arch/powerpc/kvm/powerpc.c b/arch/powerpc/kvm/powerpc.c
index b54aaa8..4b7522f 100644
--- a/arch/powerpc/kvm/powerpc.c
+++ b/arch/powerpc/kvm/powerpc.c
@@ -247,6 +247,7 @@ int kvm_dev_ioctl_check_extension(long ext)
 #ifdef CONFIG_PPC_BOOK3S_64
        case KVM_CAP_SPAPR_TCE:
        case KVM_CAP_PPC_ALLOC_HTAB:
+       case KVM_CAP_PPC_RTAS:
                r = 1;
                break;
 #endif /* CONFIG_PPC_BOOK3S_64 */
@@ -787,6 +788,7 @@ long kvm_arch_vm_ioctl(struct file *filp,
                        unsigned int ioctl, unsigned long arg)
 {
        void __user *argp = (void __user *)arg;
+       struct kvm *kvm = filp->private_data;
        long r;
 
        switch (ioctl) {
@@ -804,7 +806,6 @@ long kvm_arch_vm_ioctl(struct file *filp,
 #ifdef CONFIG_PPC_BOOK3S_64
        case KVM_CREATE_SPAPR_TCE: {
                struct kvm_create_spapr_tce create_tce;
-               struct kvm *kvm = filp->private_data;
 
                r = -EFAULT;
                if (copy_from_user(&create_tce, argp, sizeof(create_tce)))
@@ -816,7 +817,6 @@ long kvm_arch_vm_ioctl(struct file *filp,
 
 #ifdef CONFIG_KVM_BOOK3S_64_HV
        case KVM_ALLOCATE_RMA: {
-               struct kvm *kvm = filp->private_data;
                struct kvm_allocate_rma rma;
 
                r = kvm_vm_ioctl_allocate_rma(kvm, &rma);
@@ -841,6 +841,11 @@ long kvm_arch_vm_ioctl(struct file *filp,
                r = 0;
                break;
        }
+
+       case KVM_PPC_RTAS_DEFINE_TOKEN:
+               r = kvm_vm_ioctl_rtas_define_token(kvm, argp);
+               break;
+
 #endif /* CONFIG_KVM_BOOK3S_64_HV */
 
 #ifdef CONFIG_PPC_BOOK3S_64
diff --git a/include/linux/kvm.h b/include/linux/kvm.h
index 2917f0e..35c063a 100644
--- a/include/linux/kvm.h
+++ b/include/linux/kvm.h
@@ -619,6 +619,7 @@ struct kvm_ppc_smmu_info {
 #define KVM_CAP_S390_COW 79
 #define KVM_CAP_PPC_ALLOC_HTAB 80
 #define KVM_CAP_PPC_VPA 81
+#define KVM_CAP_PPC_RTAS 82
 
 #ifdef KVM_CAP_IRQ_ROUTING
 
@@ -832,6 +833,8 @@ struct kvm_s390_ucas_mapping {
 #define KVM_PPC_GET_SMMU_INFO    _IOR(KVMIO,  0xa6, struct kvm_ppc_smmu_info)
 /* Available with KVM_CAP_PPC_ALLOC_HTAB */
 #define KVM_PPC_ALLOCATE_HTAB    _IOWR(KVMIO, 0xa7, __u32)
+/* Available with KVM_CAP_PPC_RTAS */
+#define KVM_PPC_RTAS_DEFINE_TOKEN _IOW(KVMIO,  0xa8, struct 
kvm_rtas_token_args)
 
 /*
  * ioctls for vcpu fds


--
To unsubscribe from this list: send the line "unsubscribe kvm" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to