[PATCH 2/3] KVM: PPC: Add SPR emulation exits

Alexander Graf
2012-10-06T23:41:27Z

SPRs on PowerPC are the equivalent to MSRs on x86. They usually
control behavior inside a core, so the best place to emulate them
traditionally has been the kernel side of kvm.

However, some SPRs should be emulated by user space. For example
the DBCR0 register which is used for machine reset. Or the interrupt
acknowledge register on e500 which is tightly integrated with the
interrupt controller that lives in user space.

So let's expose "unknown" SPR reads and writes to user space, so that
it can handle them if it knows what's going on.

As a nice side effect, we also get a lot better error reporting up
to user space, since now we actually know when an SPR read/write failed.

Signed-off-by: Alexander Graf <agraf@suse.de>
---
Documentation/virtual/kvm/api.txt | 37 ++++++++++++++++++++++++++++++++++
arch/powerpc/include/asm/kvm_host.h | 3 ++
arch/powerpc/include/asm/kvm_ppc.h | 1 +
arch/powerpc/kvm/book3s_pr.c | 4 +++
arch/powerpc/kvm/booke.c | 4 +++
arch/powerpc/kvm/emulate.c | 38 ++++++++++++++++++++++++++++++----
arch/powerpc/kvm/powerpc.c | 20 ++++++++++++++++++
include/linux/kvm.h | 12 +++++++++++
8 files changed, 114 insertions(+), 5 deletions(-)

diff --git a/Documentation/virtual/kvm/api.txt b/Documentation/virtual/kvm/api.txt
index e726d76..7a35c64 100644
--- a/Documentation/virtual/kvm/api.txt
+++ b/Documentation/virtual/kvm/api.txt
@@ -2253,6 +2253,32 @@ The possible hypercalls are defined in the Power Architecture Platform
Requirements (PAPR) document available from www.power.org (free
developer registration required to access it).

+ /* KVM_EXIT_SPR */
+ struct {
+ __u64 sprn;
+ __u64 data;
+ __u64 msr;
+ __u8 is_write;
+#define SPR_STATUS_OK 0
+#define SPR_STATUS_FAIL 1
+ __u8 status;
+ } spr;
+
+This is used on PowerPC for Special Purpose Register emulation that
+the kernel can not deal with.
+
+It occurs when the guest triggers an mtspr or mfspr instruction on
+an SPR that is not handled by kvm's SPR emulation code. In these
+cases, 'sprn' contains the SPR ID. That ID is target CPU specific.
+'data' contains the value to write to the SPR when 'is_write'==1 (mtspr)
+or is used as result buffer for 'is_write'==0 (mfspr). Status is used
+to tell the kernel that an SPR read/write was successful. It is set to
+SPR_STATUS_OK by default. If user space fails to emulate an SPR access,
+it should set it to SPR_STATUS_FAIL, so that the kernel can inject
+an exception into the guest context. The field 'msr' contains the MSR
+register state at the point of time the SPR read/write occured. It can
+be used by user space for permission checks.
+
/* Fix the size of the union. */
char padding[256];
};
@@ -2374,3 +2400,14 @@ For mmu types KVM_MMU_FSL_BOOKE_NOHV and KVM_MMU_FSL_BOOKE_HV:
where "num_sets" is the tlb_sizes[] value divided by the tlb_ways[] value.
- The tsize field of mas1 shall be set to 4K on TLB0, even though the
hardware ignores this value for TLB0.
+
+6.4 KVM_CAP_PPC_SPR_EXIT
+
+Architectures: ppc
+Parameters: none
+Returns: 0 on success; -1 on error
+
+With this CAP enabled, KVM returns KVM_EXIT_SPR exits to have user space emulate
+SPRs that it can not handle itself.
+
+When this capability is enabled, KVM_EXIT_SPR can occur.
diff --git a/arch/powerpc/include/asm/kvm_host.h b/arch/powerpc/include/asm/kvm_host.h
index 68f5a30..d72ea96 100644
--- a/arch/powerpc/include/asm/kvm_host.h
+++ b/arch/powerpc/include/asm/kvm_host.h
@@ -510,6 +510,9 @@ struct kvm_vcpu_arch {
u8 osi_enabled;
u8 papr_enabled;
u8 watchdog_enabled;
+ u8 spr_needed;
+ u8 spr_is_write;
+ u8 spr_exit_enabled;
u8 sane;
u8 cpu_type;
u8 hcall_needed;
diff --git a/arch/powerpc/include/asm/kvm_ppc.h b/arch/powerpc/include/asm/kvm_ppc.h
index 609cca3..f9cdd42 100644
--- a/arch/powerpc/include/asm/kvm_ppc.h
+++ b/arch/powerpc/include/asm/kvm_ppc.h
@@ -42,6 +42,7 @@ enum emulation_result {
EMULATE_DONE, /* no further processing */
EMULATE_DO_MMIO, /* kvm_run filled with MMIO request */
EMULATE_DO_DCR, /* kvm_run filled with DCR request */
+ EMULATE_DO_SPR, /* kvm_run filled with SPR request */
EMULATE_FAIL, /* can't emulate this instruction */
EMULATE_AGAIN, /* something went wrong. go again */
};
diff --git a/arch/powerpc/kvm/book3s_pr.c b/arch/powerpc/kvm/book3s_pr.c
index b853696..8d4dbc8 100644
--- a/arch/powerpc/kvm/book3s_pr.c
+++ b/arch/powerpc/kvm/book3s_pr.c
@@ -748,6 +748,10 @@ program_interrupt:
run->exit_reason = KVM_EXIT_MMIO;
r = RESUME_HOST_NV;
break;
+ case EMULATE_DO_SPR:
+ run->exit_reason = KVM_EXIT_SPR;
+ r = RESUME_HOST_NV;
+ break;
default:
BUG();
}
diff --git a/arch/powerpc/kvm/booke.c b/arch/powerpc/kvm/booke.c
index 3d1f35d..07fff68 100644
--- a/arch/powerpc/kvm/booke.c
+++ b/arch/powerpc/kvm/booke.c
@@ -694,6 +694,10 @@ static int emulation_exit(struct kvm_run *run, struct kvm_vcpu *vcpu)
run->exit_reason = KVM_EXIT_DCR;
return RESUME_HOST;

+ case EMULATE_DO_SPR:
+ run->exit_reason = KVM_EXIT_SPR;
+ return RESUME_HOST;
+
case EMULATE_FAIL:
printk(KERN_CRIT "%s: emulation at %lx failed (%08x)\n",
__func__, vcpu->arch.pc, vcpu->arch.last_inst);
diff --git a/arch/powerpc/kvm/emulate.c b/arch/powerpc/kvm/emulate.c
index b0855e5..c35cec5 100644
--- a/arch/powerpc/kvm/emulate.c
+++ b/arch/powerpc/kvm/emulate.c
@@ -172,9 +172,23 @@ static int kvmppc_emulate_mtspr(struct kvm_vcpu *vcpu, int sprn, int rs)
default:
emulated = kvmppc_core_emulate_mtspr(vcpu, sprn,
spr_val);
- if (emulated == EMULATE_FAIL)
- printk(KERN_INFO "mtspr: unknown spr "
- "0x%x\n", sprn);
+ if (unlikely(emulated == EMULATE_FAIL)) {
+ if (vcpu->arch.spr_exit_enabled) {
+ /* In-kernel code can't handle this SPR.
+ Forward it on to user space */
+ vcpu->run->spr.sprn = sprn;
+ vcpu->run->spr.data = spr_val;
+ vcpu->run->spr.is_write = 1;
+ vcpu->run->spr.status = SPR_STATUS_OK;
+ vcpu->run->spr.msr = vcpu->arch.shared->msr;
+ vcpu->arch.spr_is_write = 1;
+ vcpu->arch.spr_needed = 1;
+ emulated = EMULATE_DO_SPR;
+ } else {
+ printk(KERN_INFO "mfspr: unknown spr "
+ "0x%x\n", sprn);
+ }
+ }
break;
}

@@ -237,8 +251,22 @@ static int kvmppc_emulate_mfspr(struct kvm_vcpu *vcpu, int sprn, int rt)
emulated = kvmppc_core_emulate_mfspr(vcpu, sprn,
&spr_val);
if (unlikely(emulated == EMULATE_FAIL)) {
- printk(KERN_INFO "mfspr: unknown spr "
- "0x%x\n", sprn);
+ if (vcpu->arch.spr_exit_enabled) {
+ /* In-kernel code can't handle this SPR.
+ Forward it on to user space */
+ vcpu->run->spr.sprn = sprn;
+ vcpu->run->spr.data = 0;
+ vcpu->run->spr.is_write = 0;
+ vcpu->run->spr.status = SPR_STATUS_OK;
+ vcpu->run->spr.msr = vcpu->arch.shared->msr;
+ vcpu->arch.spr_is_write = 0;
+ vcpu->arch.io_gpr = rt;
+ vcpu->arch.spr_needed = 1;
+ emulated = EMULATE_DO_SPR;
+ } else {
+ printk(KERN_INFO "mfspr: unknown spr "
+ "0x%x\n", sprn);
+ }
}
break;
}
diff --git a/arch/powerpc/kvm/powerpc.c b/arch/powerpc/kvm/powerpc.c
index deb0d59..7d120dc 100644
--- a/arch/powerpc/kvm/powerpc.c
+++ b/arch/powerpc/kvm/powerpc.c
@@ -530,6 +530,12 @@ static void kvmppc_complete_dcr_load(struct kvm_vcpu *vcpu,
kvmppc_set_gpr(vcpu, vcpu->arch.io_gpr, run->dcr.data);
}

+static void kvmppc_complete_spr_load(struct kvm_vcpu *vcpu,
+ struct kvm_run *run)
+{
+ kvmppc_set_gpr(vcpu, vcpu->arch.io_gpr, run->spr.data);
+}
+
static void kvmppc_complete_mmio_load(struct kvm_vcpu *vcpu,
struct kvm_run *run)
{
@@ -680,6 +686,16 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu, struct kvm_run *run)
if (!vcpu->arch.dcr_is_write)
kvmppc_complete_dcr_load(vcpu, run);
vcpu->arch.dcr_needed = 0;
+ } else if (vcpu->arch.spr_needed) {
+ int flags = 0;
+ if (!vcpu->arch.spr_is_write)
+ kvmppc_complete_spr_load(vcpu, run);
+#if !defined(CONFIG_PPC_BOOK3S)
+ /* Indicate an illegal instruction for BookE */
+ flags = ESR_PIL;
+#endif
+ kvmppc_core_queue_program(vcpu, flags);
+ vcpu->arch.spr_needed = 0;
} else if (vcpu->arch.osi_needed) {
u64 *gprs = run->osi.gprs;
int i;
@@ -735,6 +751,10 @@ static int kvm_vcpu_ioctl_enable_cap(struct kvm_vcpu *vcpu,
r = 0;
vcpu->arch.papr_enabled = true;
break;
+ case KVM_CAP_PPC_SPR_EXIT:
+ r = 0;
+ vcpu->arch.spr_exit_enabled = true;
+ break;
#ifdef CONFIG_BOOKE
case KVM_CAP_PPC_BOOKE_WATCHDOG:
r = 0;
diff --git a/include/linux/kvm.h b/include/linux/kvm.h
index 65ad5c6..2e8f536 100644
--- a/include/linux/kvm.h
+++ b/include/linux/kvm.h
@@ -168,6 +168,7 @@ struct kvm_pit_config {
#define KVM_EXIT_PAPR_HCALL 19
#define KVM_EXIT_S390_UCONTROL 20
#define KVM_EXIT_WATCHDOG 21
+#define KVM_EXIT_SPR 22

/* For KVM_EXIT_INTERNAL_ERROR */
#define KVM_INTERNAL_ERROR_EMULATION 1
@@ -281,6 +282,16 @@ struct kvm_run {
__u64 ret;
__u64 args[9];
} papr_hcall;
+ /* KVM_EXIT_SPR */
+ struct {
+ __u64 sprn;
+ __u64 data;
+ __u64 msr;
+ __u8 is_write;
+#define SPR_STATUS_OK 0
+#define SPR_STATUS_FAIL 1
+ __u8 status;
+ } spr;
/* Fix the size of the union. */
char padding[256];
};
@@ -630,6 +641,7 @@ struct kvm_ppc_smmu_info {
#endif
#define KVM_CAP_IRQFD_RESAMPLE 82
#define KVM_CAP_PPC_BOOKE_WATCHDOG 83
+#define KVM_CAP_PPC_SPR_EXIT 84

#ifdef KVM_CAP_IRQ_ROUTING

--
1.6.0.2