[PATCH 2/3] kvm/e500: Map (vcpu,as,id) to core's shadow id.

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

 



The shadow id mappings is based on Hollis's idea.

We can benifit a lot from this trick:

1. Support AS=1 in guest.
So that OSes other than Linux can be expected to run in the guest.

2. Minimize the frequency of TLB flushes.

Signed-off-by: Liu Yu <yu.liu@xxxxxxxxxxxxx>
---
 arch/powerpc/include/asm/kvm_e500.h |    3 +
 arch/powerpc/include/asm/kvm_ppc.h  |    1 +
 arch/powerpc/kernel/asm-offsets.c   |    1 +
 arch/powerpc/kvm/booke.h            |    4 +
 arch/powerpc/kvm/booke_interrupts.S |   11 +++
 arch/powerpc/kvm/e500_emulate.c     |   10 ++-
 arch/powerpc/kvm/e500_tlb.c         |  146 +++++++++++++++++++++++++++++++++--
 arch/powerpc/kvm/e500_tlb.h         |    2 +
 8 files changed, 167 insertions(+), 11 deletions(-)

diff --git a/arch/powerpc/include/asm/kvm_e500.h b/arch/powerpc/include/asm/kvm_e500.h
index ffa111b..e5ca811 100644
--- a/arch/powerpc/include/asm/kvm_e500.h
+++ b/arch/powerpc/include/asm/kvm_e500.h
@@ -43,6 +43,9 @@ struct kvmppc_vcpu_e500 {
 	/* Pages which are referenced in the shadow TLB. */
 	struct kvmppc_e500_shadow_ref *shadow_refs[E500_TLB_NUM];
 
+	/* MMU id mapping */
+	void *id_mapping;
+
 	unsigned int guest_tlb_size[E500_TLB_NUM];
 	unsigned int shadow_tlb_size[E500_TLB_NUM];
 	unsigned int guest_tlb_nv[E500_TLB_NUM];
diff --git a/arch/powerpc/include/asm/kvm_ppc.h b/arch/powerpc/include/asm/kvm_ppc.h
index 2c6ee34..40823c4 100644
--- a/arch/powerpc/include/asm/kvm_ppc.h
+++ b/arch/powerpc/include/asm/kvm_ppc.h
@@ -57,6 +57,7 @@ extern void kvmppc_emulate_dec(struct kvm_vcpu *vcpu);
 extern void kvmppc_mmu_map(struct kvm_vcpu *vcpu, u64 gvaddr, gpa_t gpaddr,
                            unsigned int gtlb_idx);
 extern void kvmppc_mmu_priv_switch(struct kvm_vcpu *vcpu, int usermode);
+extern void kvmppc_mmu_as_switch(struct kvm_vcpu *vcpu, unsigned int as);
 extern void kvmppc_mmu_switch_pid(struct kvm_vcpu *vcpu, u32 pid);
 extern void kvmppc_mmu_destroy(struct kvm_vcpu *vcpu);
 extern int kvmppc_mmu_dtlb_index(struct kvm_vcpu *vcpu, gva_t eaddr);
diff --git a/arch/powerpc/kernel/asm-offsets.c b/arch/powerpc/kernel/asm-offsets.c
index 42fe4da..89d28df 100644
--- a/arch/powerpc/kernel/asm-offsets.c
+++ b/arch/powerpc/kernel/asm-offsets.c
@@ -379,6 +379,7 @@ int main(void)
 	DEFINE(VCPU_LAST_INST, offsetof(struct kvm_vcpu, arch.last_inst));
 	DEFINE(VCPU_FAULT_DEAR, offsetof(struct kvm_vcpu, arch.fault_dear));
 	DEFINE(VCPU_FAULT_ESR, offsetof(struct kvm_vcpu, arch.fault_esr));
+	DEFINE(VCPU_SWAP_PID, offsetof(struct kvm_vcpu, arch.swap_pid));
 #endif
 #ifdef CONFIG_44x
 	DEFINE(PGD_T_LOG2, PGD_T_LOG2);
diff --git a/arch/powerpc/kvm/booke.h b/arch/powerpc/kvm/booke.h
index d59bcca..96e6cc0 100644
--- a/arch/powerpc/kvm/booke.h
+++ b/arch/powerpc/kvm/booke.h
@@ -57,6 +57,10 @@ static inline void kvmppc_set_msr(struct kvm_vcpu *vcpu, u32 new_msr)
 	if ((new_msr & MSR_PR) != (vcpu->arch.msr & MSR_PR))
 		kvmppc_mmu_priv_switch(vcpu, new_msr & MSR_PR);
 
+	if ((new_msr & (MSR_IS | MSR_DS)) !=
+			(vcpu->arch.msr & (MSR_IS | MSR_DS)))
+		kvmppc_mmu_as_switch(vcpu, new_msr & (MSR_IS | MSR_DS));
+
 	vcpu->arch.msr = new_msr;
 
 	if (vcpu->arch.msr & MSR_WE) {
diff --git a/arch/powerpc/kvm/booke_interrupts.S b/arch/powerpc/kvm/booke_interrupts.S
index d0c6f84..12383fe 100644
--- a/arch/powerpc/kvm/booke_interrupts.S
+++ b/arch/powerpc/kvm/booke_interrupts.S
@@ -192,6 +192,12 @@ _GLOBAL(kvmppc_resume_host)
 	lwz	r3, VCPU_HOST_PID(r4)
 	mtspr	SPRN_PID, r3
 
+#ifdef CONFIG_E500
+	/* we cheat and know Linux doesn't use PID1 which is always 0 */
+	lis	r3, 0
+	mtspr	SPRN_PID1, r3
+#endif
+
 	/* Restore host IVPR before re-enabling interrupts. We cheat and know
 	 * that Linux IVPR is always 0xc0000000. */
 	lis	r3, 0xc000
@@ -350,6 +356,11 @@ lightweight_exit:
 	lwz	r3, VCPU_SHADOW_PID(r4)
 	mtspr	SPRN_PID, r3
 
+#ifdef CONFIG_E500
+	lwz	r3, VCPU_SWAP_PID(r4)
+	mtspr	SPRN_PID1, r3
+#endif
+
 #ifdef CONFIG_44x
 	iccci	0, 0 /* XXX hack */
 #endif
diff --git a/arch/powerpc/kvm/e500_emulate.c b/arch/powerpc/kvm/e500_emulate.c
index 3f76041..8d85655 100644
--- a/arch/powerpc/kvm/e500_emulate.c
+++ b/arch/powerpc/kvm/e500_emulate.c
@@ -76,10 +76,14 @@ int kvmppc_core_emulate_mtspr(struct kvm_vcpu *vcpu, int sprn, int rs)
 	int emulated = EMULATE_DONE;
 
 	switch (sprn) {
-	case SPRN_PID:
-		vcpu_e500->pid[0] = vcpu->arch.shadow_pid =
-			vcpu->arch.pid = vcpu->arch.gpr[rs];
+	case SPRN_PID: {
+		unsigned int as = !!(vcpu->arch.msr & (MSR_IS | MSR_DS));
+
+		vcpu_e500->pid[0] = vcpu->arch.pid = vcpu->arch.gpr[rs];
+		vcpu->arch.shadow_pid = kvmppc_e500_get_sid(vcpu_e500, as,
+						    get_cur_pid(vcpu));
 		break;
+	}
 	case SPRN_PID1:
 		vcpu_e500->pid[1] = vcpu->arch.gpr[rs]; break;
 	case SPRN_PID2:
diff --git a/arch/powerpc/kvm/e500_tlb.c b/arch/powerpc/kvm/e500_tlb.c
index 847dfec..7b614a9 100644
--- a/arch/powerpc/kvm/e500_tlb.c
+++ b/arch/powerpc/kvm/e500_tlb.c
@@ -27,6 +27,101 @@
 
 static unsigned int tlb1_entry_num;
 
+struct id_mapping {
+	unsigned char id[2][256];
+};
+
+struct shadow_id {
+	void *ref;
+};
+
+static DEFINE_PER_CPU(struct shadow_id[256], host_sid);
+
+static inline int e500_id_create_mapping(unsigned char *entry)
+{
+	unsigned long sid;
+
+	preempt_disable();
+	sid = (unsigned long)++(__get_cpu_var(host_sid)[0].ref);
+	if (sid < 256) {
+		*entry = (unsigned char)sid;
+		__get_cpu_var(host_sid)[sid].ref = entry;
+		return sid;
+	}
+	preempt_enable();
+
+	return -1;
+}
+
+static inline void e500_id_destroy_all(void)
+{
+	preempt_disable();
+	memset(__get_cpu_var(host_sid), 0, sizeof(__get_cpu_var(host_sid)));
+	preempt_enable();
+}
+
+static inline int e500_id_find_mapping(unsigned char *entry)
+{
+	if (*entry && __get_cpu_var(host_sid)[*entry].ref == entry)
+		return *entry;
+	return -1;
+}
+
+static int kvmppc_e500_alloc_idm(struct kvmppc_vcpu_e500 *vcpu_e500)
+{
+	vcpu_e500->id_mapping =
+	    (struct id_mapping *)kzalloc(sizeof(struct id_mapping), GFP_KERNEL);
+	if (vcpu_e500->id_mapping != NULL)
+		return 0;
+	return -1;
+}
+
+static void kvmppc_e500_free_idm(struct kvmppc_vcpu_e500 *vcpu_e500)
+{
+	kfree(vcpu_e500->id_mapping);
+	vcpu_e500->id_mapping = NULL;
+	return;
+}
+
+static inline void kvmppc_e500_reset_idm(struct kvmppc_vcpu_e500 *vcpu_e500)
+{
+	memset(vcpu_e500->id_mapping, 0, sizeof(struct id_mapping));
+}
+
+static void inline kvmppc_e500_update_spid(struct kvmppc_vcpu_e500 *vcpu_e500)
+{
+	unsigned int as = !!(vcpu_e500->vcpu.arch.msr & (MSR_IS | MSR_DS));
+
+	vcpu_e500->vcpu.arch.shadow_pid = kvmppc_e500_get_sid(vcpu_e500, as,
+			get_cur_pid(&vcpu_e500->vcpu));
+	vcpu_e500->vcpu.arch.swap_pid = kvmppc_e500_get_sid(vcpu_e500, as, 0);
+}
+
+/*
+ * Map guest (vcpu,as,id) to individual shadow id.
+ */
+unsigned int kvmppc_e500_get_sid(struct kvmppc_vcpu_e500 *vcpu_e500,
+				  int as, int gid)
+{
+	struct id_mapping *idm = vcpu_e500->id_mapping;
+	int sid;
+
+	sid = e500_id_find_mapping(&idm->id[as][gid]);
+
+	while (sid <= 0) {
+		/* None mapping yet */
+		sid = e500_id_create_mapping(&idm->id[as][gid]);
+		if(sid <= 0) {
+			BUG_ON(sid == 0);
+			_tlbil_all();
+			e500_id_destroy_all();
+			kvmppc_e500_update_spid(vcpu_e500);
+		}
+	}
+
+	return sid;
+}
+
 void kvmppc_dump_tlbs(struct kvm_vcpu *vcpu)
 {
 	struct kvmppc_vcpu_e500 *vcpu_e500 = to_e500(vcpu);
@@ -131,11 +226,14 @@ static inline void write_host_tlbe(struct kvmppc_vcpu_e500 *vcpu_e500,
 
 void kvmppc_e500_tlb_load(struct kvm_vcpu *vcpu, int cpu)
 {
+	struct kvmppc_vcpu_e500 *vcpu_e500 = to_e500(vcpu);
+
+	/* Shadow PID may be expired */
+	kvmppc_e500_update_spid(vcpu_e500);
 }
 
 void kvmppc_e500_tlb_put(struct kvm_vcpu *vcpu)
 {
-	_tlbil_all();
 }
 
 /* Search the guest TLB for a matching entry. */
@@ -245,10 +343,14 @@ static inline void kvmppc_e500_setup_stlbe(struct kvmppc_vcpu_e500 *vcpu_e500,
 		u64 gvaddr, struct tlbe *stlbe)
 {
 	hpa_t hpaddr = page_to_phys(ref->page);
+	unsigned int stid;
+
+	stid = kvmppc_e500_get_sid(vcpu_e500, get_tlb_ts(gtlbe),
+				get_tlb_tid(gtlbe));
 
 	/* Force TS=1 IPROT=0 TSIZE=4KB for all guest mappings. */
 	stlbe->mas1 = MAS1_TSIZE(BOOKE_PAGESZ_4K)
-		| MAS1_TID(get_tlb_tid(gtlbe)) | MAS1_TS | MAS1_VALID;
+		| MAS1_TID(stid) | MAS1_TS | MAS1_VALID;
 	stlbe->mas2 = (gvaddr & MAS2_EPN)
 		| e500_shadow_mas2_attrib(gtlbe->mas2,
 				vcpu_e500->vcpu.arch.msr & MSR_PR);
@@ -315,11 +417,29 @@ static int kvmppc_e500_tlb1_map(struct kvmppc_vcpu_e500 *vcpu_e500,
  * proper permission bits. */
 void kvmppc_mmu_priv_switch(struct kvm_vcpu *vcpu, int usermode)
 {
+	struct kvmppc_vcpu_e500 *vcpu_e500 = to_e500(vcpu);
+
 	if (usermode) {
-		_tlbil_all();
+		/* clear PID for guest kernel mapping */
+		vcpu->arch.swap_pid = 0;
+	} else {
+		/* set PID for guest kernel mapping
+		 * We assume:
+		 * 1. AS  = 0 when enter supervise mode
+		 * 2. TID = 0 for supervise code/data mappings */
+		vcpu->arch.swap_pid = kvmppc_e500_get_sid(vcpu_e500, 0, 0);
 	}
 }
 
+void kvmppc_mmu_as_switch(struct kvm_vcpu *vcpu, u32 new_as)
+{
+	struct kvmppc_vcpu_e500 *vcpu_e500 = to_e500(vcpu);
+	u32 pid = get_cur_pid(vcpu);
+
+	vcpu->arch.shadow_pid = kvmppc_e500_get_sid(vcpu_e500, !!new_as, pid);
+	vcpu->arch.swap_pid = kvmppc_e500_get_sid(vcpu_e500, !!new_as, 0);
+}
+
 static inline int kvmppc_e500_gtlbe_invalidate(
 				struct kvmppc_vcpu_e500 *vcpu_e500,
 				int tlbsel, int esel)
@@ -345,7 +465,9 @@ int kvmppc_e500_emul_mt_mmucsr0(struct kvmppc_vcpu_e500 *vcpu_e500, ulong value)
 		for (esel = 0; esel < vcpu_e500->guest_tlb_size[1]; esel++)
 			kvmppc_e500_gtlbe_invalidate(vcpu_e500, 1, esel);
 
-	_tlbil_all();
+	/* Reset vcpu shadow id mapping */
+	kvmppc_e500_reset_idm(vcpu_e500);
+	kvmppc_e500_update_spid(vcpu_e500);
 
 	return EMULATE_DONE;
 }
@@ -376,7 +498,9 @@ int kvmppc_e500_emul_tlbivax(struct kvm_vcpu *vcpu, int ra, int rb)
 			kvmppc_e500_gtlbe_invalidate(vcpu_e500, tlbsel, esel);
 	}
 
-	_tlbil_all();
+	/* Reset vcpu shadow id mapping */
+	kvmppc_e500_reset_idm(vcpu_e500);
+	kvmppc_e500_update_spid(vcpu_e500);
 
 	return EMULATE_DONE;
 }
@@ -555,9 +679,6 @@ void kvmppc_mmu_destroy(struct kvm_vcpu *vcpu)
 	for (stlbsel = 0; stlbsel < 2; stlbsel++)
 		for (i = 0; i < vcpu_e500->guest_tlb_size[stlbsel]; i++)
 			kvmppc_e500_shadow_release(vcpu_e500, stlbsel, i);
-
-	/* discard all guest mapping */
-	_tlbil_all();
 }
 
 void kvmppc_mmu_map(struct kvm_vcpu *vcpu, u64 eaddr, gpa_t gpaddr,
@@ -629,6 +750,9 @@ void kvmppc_e500_tlb_setup(struct kvmppc_vcpu_e500 *vcpu_e500)
 	tlbe->mas2 = (0xe0004500 & 0xFFFFF000) | MAS2_I | MAS2_G;
 	tlbe->mas3 = (0xe0004500 & 0xFFFFF000) | E500_TLB_SUPER_PERM_MASK;
 	tlbe->mas7 = 0;
+
+	/* Setup shadow PID before start guest */
+	kvmppc_e500_update_spid(vcpu_e500);
 }
 
 int kvmppc_e500_tlb_init(struct kvmppc_vcpu_e500 *vcpu_e500)
@@ -657,8 +781,13 @@ int kvmppc_e500_tlb_init(struct kvmppc_vcpu_e500 *vcpu_e500)
 	if (vcpu_e500->shadow_refs[1] == NULL)
 		goto err_out_ref0;
 
+	if(kvmppc_e500_alloc_idm(vcpu_e500) < 0)
+		goto err_out_ref1;
+
 	return 0;
 
+err_out_ref1:
+	kfree(vcpu_e500->shadow_refs[1]);
 err_out_ref0:
 	kfree(vcpu_e500->shadow_refs[0]);
 err_out_guest1:
@@ -671,6 +800,7 @@ err_out:
 
 void kvmppc_e500_tlb_uninit(struct kvmppc_vcpu_e500 *vcpu_e500)
 {
+	kvmppc_e500_free_idm(vcpu_e500);
 	kfree(vcpu_e500->shadow_refs[1]);
 	kfree(vcpu_e500->shadow_refs[0]);
 	kfree(vcpu_e500->guest_tlb[1]);
diff --git a/arch/powerpc/kvm/e500_tlb.h b/arch/powerpc/kvm/e500_tlb.h
index 45b064b..5c6e56c 100644
--- a/arch/powerpc/kvm/e500_tlb.h
+++ b/arch/powerpc/kvm/e500_tlb.h
@@ -55,6 +55,8 @@ extern void kvmppc_e500_tlb_load(struct kvm_vcpu *, int);
 extern int kvmppc_e500_tlb_init(struct kvmppc_vcpu_e500 *);
 extern void kvmppc_e500_tlb_uninit(struct kvmppc_vcpu_e500 *);
 extern void kvmppc_e500_tlb_setup(struct kvmppc_vcpu_e500 *);
+extern unsigned int kvmppc_e500_get_sid(struct kvmppc_vcpu_e500 *vcpu_e500,
+					int as, int gid);
 
 /* TLB helper functions */
 static inline unsigned int get_tlb_size(const struct tlbe *tlbe)
-- 
1.5.4

--
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