[PATCH 3/6] kvmppc: rewrite guest code - sprg0-3

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: Christian Ehrhardt <ehrhardt@xxxxxxxxxxxxxxxxxx>

This patch adds the functionality of rewriting guest code using the magic page
mechanism. If a paravirtual guest memory (magic page) is registered the host
rewrites trapping emulations instead of emulating them. This only works with
instructions that can be rewritten with one instruction.
On registration of the paravirtual memory the values have to be synced so that
the old emulated values can be found in the magic page now and are ready to be
accessed by the guest.
For speed optimization the rewriting is done only if normal emulation set a
rewritable flag, saving unneccesary calls and checks for every emulation. On the
other hand this implies that the normal emulation was done with out of sync
(non-pv) values. Therefor a second emulation based on pv values is called
prior to the instruction being rewritten. This simplifies the frequent path of
normal instruction emulation by increasing the path when an instruction is
rewritten which is only executed once per rewritable instruction in the guest
code.
The rewriting of guest code is guest visible (the guest already agrees on this
by registering a magic page) and is driven by the program interrupts usually
used to emulate these instructions in the hypervisor.
This patch replaces m[ft]spr SPRG[0-3] instructions with stw (write) and lwz
(read) instructions.

Signed-off-by: Christian Ehrhardt <ehrhardt@xxxxxxxxxxxxxxxxxx>
---

[diffstat]
 arch/powerpc/kvm/booke_guest.c |   14 ++
 arch/powerpc/kvm/emulate.c     |  279 +++++++++++++++++++++++++++++++++++++++--
 include/asm-powerpc/kvm_para.h |   25 +++
 3 files changed, 310 insertions(+), 8 deletions(-)

[diff]

diff --git a/arch/powerpc/kvm/booke_guest.c b/arch/powerpc/kvm/booke_guest.c
--- a/arch/powerpc/kvm/booke_guest.c
+++ b/arch/powerpc/kvm/booke_guest.c
@@ -544,6 +544,13 @@
 	for (i = 0; i < ARRAY_SIZE(regs->gpr); i++)
 		regs->gpr[i] = vcpu->arch.gpr[i];
 
+	if (kvmppc_has_pvmem(vcpu)) {
+		regs->sprg0 = kvmppc_get_pvreg(vcpu, KVM_PPCPV_OFFSET_SPRG0);
+		regs->sprg1 = kvmppc_get_pvreg(vcpu, KVM_PPCPV_OFFSET_SPRG1);
+		regs->sprg2 = kvmppc_get_pvreg(vcpu, KVM_PPCPV_OFFSET_SPRG2);
+		regs->sprg3 = kvmppc_get_pvreg(vcpu, KVM_PPCPV_OFFSET_SPRG3);
+	}
+
 	return 0;
 }
 
@@ -569,6 +576,13 @@
 
 	for (i = 0; i < ARRAY_SIZE(vcpu->arch.gpr); i++)
 		vcpu->arch.gpr[i] = regs->gpr[i];
+
+	if (kvmppc_has_pvmem(vcpu)) {
+		kvmppc_set_pvreg(vcpu, KVM_PPCPV_OFFSET_SPRG0, regs->sprg0);
+		kvmppc_set_pvreg(vcpu, KVM_PPCPV_OFFSET_SPRG1, regs->sprg1);
+		kvmppc_set_pvreg(vcpu, KVM_PPCPV_OFFSET_SPRG2, regs->sprg2);
+		kvmppc_set_pvreg(vcpu, KVM_PPCPV_OFFSET_SPRG3, regs->sprg3);
+	}
 
 	return 0;
 }
diff --git a/arch/powerpc/kvm/emulate.c b/arch/powerpc/kvm/emulate.c
--- a/arch/powerpc/kvm/emulate.c
+++ b/arch/powerpc/kvm/emulate.c
@@ -29,6 +29,7 @@
 #include <asm/dcr-regs.h>
 #include <asm/time.h>
 #include <asm/byteorder.h>
+#include <asm/system.h>
 #include <asm/kvm_ppc.h>
 
 #include "44x_tlb.h"
@@ -87,6 +88,37 @@
 static inline unsigned int get_d(u32 inst)
 {
 	return inst & 0xffff;
+}
+
+static inline void set_op(u32 op, u32 *inst)
+{
+	*inst |= ((op & 0x3f) << 26);
+	return;
+}
+
+static inline void set_ra(u32 ra, u32 *inst)
+{
+	*inst |= ((ra & 0x1f) << 16);
+	return;
+}
+
+static inline void set_rs(u32 rs, u32 *inst)
+{
+	*inst |= ((rs & 0x1f) << 21);
+	return;
+}
+
+static inline void set_rt(u32 rt, u32 *inst)
+{
+	*inst |= ((rt & 0x1f) << 21);
+	return;
+}
+
+static inline void set_d(s16 d, u32 *inst)
+{
+	/* mask in last 16 signed bits */
+	*inst |= ((u32)d & 0xFFFF);
+	return;
 }
 
 static int tlbe_is_host_safe(const struct kvm_vcpu *vcpu,
@@ -212,11 +244,25 @@
 
 	switch (vcpu->arch.gpr[11]) {
 	case KVM_HCALL_RESERVE_MAGICPAGE:
+		/* FIXME assert : we don't support this on smp guests */
+
 		vcpu->arch.pvmem_gvaddr = vcpu->arch.gpr[3];
 		vcpu->arch.pvmem_gpaddr = vcpu->arch.gpr[4];
 		pvmem_page = gfn_to_page(vcpu->kvm,
 			vcpu->arch.pvmem_gpaddr >> KVM_PPCPV_MAGIC_PAGE_SHIFT);
 		vcpu->arch.pvmem = kmap(pvmem_page);
+
+		/* on enablement of pvmem support we need to sync all values
+		 * into the pvmem area to be able so correcty fulfill read
+		 * acesses that might occur prior to writes */
+		kvmppc_set_pvreg(vcpu, KVM_PPCPV_OFFSET_SPRG0,
+					vcpu->arch.sprg0);
+		kvmppc_set_pvreg(vcpu, KVM_PPCPV_OFFSET_SPRG1,
+					vcpu->arch.sprg1);
+		kvmppc_set_pvreg(vcpu, KVM_PPCPV_OFFSET_SPRG2,
+					vcpu->arch.sprg2);
+		kvmppc_set_pvreg(vcpu, KVM_PPCPV_OFFSET_SPRG3,
+					vcpu->arch.sprg3);
 		break;
 	default:
 		printk(KERN_ERR "unknown hypercall %d\n", vcpu->arch.gpr[11]);
@@ -226,6 +272,200 @@
 
 	vcpu->arch.gpr[3] = ret;
 	return ret;
+}
+
+int kvmppc_pvmem_rewrite_instruction(struct kvm_vcpu *vcpu)
+{
+	int err = 0;
+	u32 inst = vcpu->arch.last_inst;
+	int sprn;
+	int rw = -1;
+
+	int offset = -1;
+
+	switch (get_op(inst)) {
+	case 31:
+		switch (get_xop(inst)) {
+		case 339:                                       /* mfspr */
+			sprn = get_sprn(inst);
+			rw = KVM_PPCPV_PVMEM_READ;
+			switch (sprn) {
+			case SPRN_SPRG0:
+				offset = KVM_PPCPV_OFFSET_SPRG0;
+				break;
+			case SPRN_SPRG1:
+				offset = KVM_PPCPV_OFFSET_SPRG1;
+				break;
+			case SPRN_SPRG2:
+				offset = KVM_PPCPV_OFFSET_SPRG2;
+				break;
+			case SPRN_SPRG3:
+				offset = KVM_PPCPV_OFFSET_SPRG3;
+				break;
+			default:
+				err = -EFAULT;
+			}
+			break;
+		case 467:                                       /* mtspr */
+			sprn = get_sprn(inst);
+			rw = KVM_PPCPV_PVMEM_WRITE;
+			switch (sprn) {
+			case SPRN_SPRG0:
+				offset = KVM_PPCPV_OFFSET_SPRG0;
+				break;
+			case SPRN_SPRG1:
+				offset = KVM_PPCPV_OFFSET_SPRG1;
+				break;
+			case SPRN_SPRG2:
+				offset = KVM_PPCPV_OFFSET_SPRG2;
+				break;
+			case SPRN_SPRG3:
+				offset = KVM_PPCPV_OFFSET_SPRG3;
+				break;
+			default:
+				err = -EFAULT;
+			}
+			break;
+		default:
+			err = -EFAULT;
+		}
+		break;
+	default:
+		err = -EFAULT;
+	}
+
+	if (!err) {
+		u32 newinst = 0;
+		struct page *pcpage;
+		void *pcmem;
+		unsigned long pcaddr;
+		s16 displacement = 0;
+		gpa_t guest_pcaddr;
+		struct tlbe *gtlbe;
+
+		BUG_ON(offset == -1);
+		/* calculate displacement of gvaddr+offset from 0 */
+		displacement += (vcpu->arch.pvmem_gvaddr & 0xFFFF);
+		displacement += offset;
+
+		BUG_ON(rw == -1);
+		if (rw == KVM_PPCPV_PVMEM_READ) {
+			set_op(32, &newinst); /* lwz */
+			set_rt(get_rt(inst), &newinst); /* set original rt */
+		} else {
+			set_op(36, &newinst); /* stw */
+			set_rs(get_rs(inst), &newinst); /* set original rs */
+		}
+		set_ra(0, &newinst); /* ra = 0 -> base addr 0 (no reg needed) */
+		set_d(displacement, &newinst); /* set displacement from 0 */
+
+		gtlbe = kvmppc_44x_itlb_search(vcpu, vcpu->arch.pc);
+		if (!gtlbe)
+			return -EFAULT;
+		guest_pcaddr = tlb_xlate(gtlbe, vcpu->arch.pc);
+
+		down_read(&current->mm->mmap_sem);
+		pcpage = gfn_to_page(vcpu->kvm,
+				guest_pcaddr >> KVM_PPCPV_MAGIC_PAGE_SHIFT);
+		up_read(&current->mm->mmap_sem);
+		if (pcpage == bad_page)
+			return -EFAULT;
+
+		pcmem = kmap_atomic(pcpage, KM_USER0);
+		if (!pcmem)
+			return -EFAULT;
+		pcaddr = ((unsigned long)pcmem
+				| (vcpu->arch.pc & KVM_PPCPV_MAGIC_PAGE_MASK));
+		BUG_ON(inst != *((u32 *)pcaddr));
+		create_instruction(pcaddr, newinst);
+		kunmap_atomic(pcmem, KM_USER0);
+	}
+
+	return err;
+}
+
+/*
+ * This is the last emulation before the instruction is rewritten using the
+ * magic page. At this point the correct data is already in the paravirtual
+ * magic page so the result of "normal" emulation would be out of sync.
+ * This simplifies the code of non pv emulation but is only valid as long
+ * as the paravirtualized instructions have no side effects.
+ * valid example: "GPR[RT] <- value of sprn", this can be done normal + pv
+ * style afterwards and eventually has the correct (pv) value.
+ */
+int kvmppc_emulate_instruction_pv(struct kvm_vcpu *vcpu)
+{
+	u32 inst = vcpu->arch.last_inst;
+	int rs;
+	int rt;
+	int sprn;
+	enum emulation_result emulated = EMULATE_DONE;
+
+	switch (get_op(inst)) {
+	case 31:
+		switch (get_xop(inst)) {
+		case 339:                                       /* mfspr */
+			sprn = get_sprn(inst);
+			rt = get_rt(inst);
+			switch (sprn) {
+			case SPRN_SPRG0:
+				vcpu->arch.gpr[rt] = kvmppc_get_pvreg(vcpu,
+						KVM_PPCPV_OFFSET_SPRG0);
+				break;
+			case SPRN_SPRG1:
+				vcpu->arch.gpr[rt] = kvmppc_get_pvreg(vcpu,
+						KVM_PPCPV_OFFSET_SPRG1);
+				break;
+			case SPRN_SPRG2:
+				vcpu->arch.gpr[rt] = kvmppc_get_pvreg(vcpu,
+						KVM_PPCPV_OFFSET_SPRG2);
+				break;
+			case SPRN_SPRG3:
+				vcpu->arch.gpr[rt] = kvmppc_get_pvreg(vcpu,
+						KVM_PPCPV_OFFSET_SPRG3);
+				break;
+			default:
+				printk(KERN_ERR"mfspr: unknown spr %x\n", sprn);
+				vcpu->arch.gpr[rt] = 0;
+				break;
+			}
+			break;
+
+		case 467:                                       /* mtspr */
+			sprn = get_sprn(inst);
+			rs = get_rs(inst);
+			switch (sprn) {
+			case SPRN_SPRG0:
+				kvmppc_set_pvreg(vcpu, KVM_PPCPV_OFFSET_SPRG0,
+						vcpu->arch.gpr[rs]);
+				break;
+			case SPRN_SPRG1:
+				kvmppc_set_pvreg(vcpu, KVM_PPCPV_OFFSET_SPRG1,
+						vcpu->arch.gpr[rs]);
+				break;
+			case SPRN_SPRG2:
+				kvmppc_set_pvreg(vcpu, KVM_PPCPV_OFFSET_SPRG2,
+						vcpu->arch.gpr[rs]);
+				break;
+			case SPRN_SPRG3:
+				kvmppc_set_pvreg(vcpu, KVM_PPCPV_OFFSET_SPRG3,
+						vcpu->arch.gpr[rs]);
+				break;
+			default:
+				printk(KERN_ERR"mtspr: unknown spr %x\n", sprn);
+				emulated = EMULATE_FAIL;
+				break;
+			}
+			break;
+		}
+		break;
+	default:
+		printk(KERN_ERR"unknown op %d\n", get_op(inst));
+		emulated = EMULATE_FAIL;
+		break;
+	}
+
+	return emulated;
 }
 
 /* XXX to do:
@@ -255,6 +495,7 @@
 	int dcrn;
 	enum emulation_result emulated = EMULATE_DONE;
 	int advance = 1;
+	int rewritable = 0;
 
 	switch (get_op(inst)) {
 	case 3:                                                 /* trap */
@@ -432,13 +673,21 @@
 				vcpu->arch.gpr[rt] = mftbu(); break;
 
 			case SPRN_SPRG0:
-				vcpu->arch.gpr[rt] = vcpu->arch.sprg0; break;
+				vcpu->arch.gpr[rt] = vcpu->arch.sprg0;
+				rewritable = 1;
+				break;
 			case SPRN_SPRG1:
-				vcpu->arch.gpr[rt] = vcpu->arch.sprg1; break;
+				vcpu->arch.gpr[rt] = vcpu->arch.sprg1;
+				rewritable = 1;
+				break;
 			case SPRN_SPRG2:
-				vcpu->arch.gpr[rt] = vcpu->arch.sprg2; break;
+				vcpu->arch.gpr[rt] = vcpu->arch.sprg2;
+				rewritable = 1;
+				break;
 			case SPRN_SPRG3:
-				vcpu->arch.gpr[rt] = vcpu->arch.sprg3; break;
+				vcpu->arch.gpr[rt] = vcpu->arch.sprg3;
+				rewritable = 1;
+				break;
 			/* Note: SPRG4-7 are user-readable, so we don't get
 			 * a trap. */
 
@@ -570,13 +819,21 @@
 				break;
 
 			case SPRN_SPRG0:
-				vcpu->arch.sprg0 = vcpu->arch.gpr[rs]; break;
+				vcpu->arch.sprg0 = vcpu->arch.gpr[rs];
+				rewritable = 1;
+				break;
 			case SPRN_SPRG1:
-				vcpu->arch.sprg1 = vcpu->arch.gpr[rs]; break;
+				vcpu->arch.sprg1 = vcpu->arch.gpr[rs];
+				rewritable = 1;
+				break;
 			case SPRN_SPRG2:
-				vcpu->arch.sprg2 = vcpu->arch.gpr[rs]; break;
+				vcpu->arch.sprg2 = vcpu->arch.gpr[rs];
+				rewritable = 1;
+				break;
 			case SPRN_SPRG3:
-				vcpu->arch.sprg3 = vcpu->arch.gpr[rs]; break;
+				vcpu->arch.sprg3 = vcpu->arch.gpr[rs];
+				rewritable = 1;
+				break;
 
 			/* Note: SPRG4-7 are user-readable. These values are
 			 * loaded into the real SPRGs when resuming the
@@ -800,6 +1057,12 @@
 
 	KVMTRACE_3D(PPC_INSTR, vcpu, inst, vcpu->arch.pc, emulated, entryexit);
 
+	if (unlikely(rewritable && kvmppc_has_pvmem(vcpu))) {
+		/* last emul has to be done with the pv values */
+		emulated = kvmppc_emulate_instruction_pv(vcpu);
+		kvmppc_pvmem_rewrite_instruction(vcpu);
+	}
+
 	if (advance)
 		vcpu->arch.pc += 4; /* Advance past emulated instruction. */
 
diff --git a/include/asm-powerpc/kvm_para.h b/include/asm-powerpc/kvm_para.h
--- a/include/asm-powerpc/kvm_para.h
+++ b/include/asm-powerpc/kvm_para.h
@@ -34,7 +34,16 @@
  */
 #define KVM_PPCPV_MAGIC_PAGE_SIZE	4096
 #define KVM_PPCPV_MAGIC_PAGE_SHIFT	12
+#define KVM_PPCPV_MAGIC_PAGE_MASK	0xFFF
 #define KVM_PPCPV_MAGIC_PAGE_FLAGS	0x3f
+
+#define KVM_PPCPV_PVMEM_READ 1
+#define KVM_PPCPV_PVMEM_WRITE 2
+
+#define KVM_PPCPV_OFFSET_SPRG0	0x00
+#define KVM_PPCPV_OFFSET_SPRG1	0x04
+#define KVM_PPCPV_OFFSET_SPRG2	0x08
+#define KVM_PPCPV_OFFSET_SPRG3	0x0C
 
 static inline int kvm_para_available(void)
 {
@@ -60,6 +69,22 @@
 	return !!vcpu->arch.pvmem;
 }
 
+static inline u32 kvmppc_get_pvreg(struct kvm_vcpu *vcpu, int offset)
+{
+	u32 *pvreg;
+
+	pvreg = vcpu->arch.pvmem + offset;
+	return *pvreg;
+}
+
+static inline void kvmppc_set_pvreg(struct kvm_vcpu *vcpu, int offset, u32 val)
+{
+	u32 *pvreg;
+
+	pvreg = vcpu->arch.pvmem + offset;
+	*pvreg = val;
+}
+
 #endif /* __KERNEL__ */
 
 #endif /* __POWERPC_KVM_PARA_H__ */
--
To unsubscribe from this list: send the line "unsubscribe kvm-ppc" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [KVM Development]     [KVM ARM]     [KVM ia64]     [Linux Virtualization]     [Linux USB Devel]     [Linux Video]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Big List of Linux Books]

  Powered by Linux