Skip to content

Commit a12ab13

Browse files
Marc Zyngieroupton
authored andcommitted
KVM: arm64: Use local TLBI on permission relaxation
Broadcast TLB invalidations (TLBIs) targeting the Inner Shareable Domain are usually less performant than their non-shareable variant. In particular, we observed some implementations that take millliseconds to complete parallel broadcasted TLBIs. It's safe to use non-shareable TLBIs when relaxing permissions on a PTE in the KVM case. According to the ARM ARM (0487I.a) section D8.13.1 "Using break-before-make when updating translation table entries", permission relaxation does not need break-before-make. Specifically, R_WHZWS states that these are the only changes that require a break-before-make sequence: changes of memory type (Shareability or Cacheability), address changes, or changing the block size. Signed-off-by: Marc Zyngier <maz@kernel.org> Signed-off-by: Ricardo Koller <ricarkol@google.com> Reviewed-by: Gavin Shan <gshan@redhat.com> Link: https://lore.kernel.org/r/20230426172330.1439644-13-ricarkol@google.com Signed-off-by: Oliver Upton <oliver.upton@linux.dev>
1 parent 6acf516 commit a12ab13

5 files changed

Lines changed: 99 additions & 1 deletion

File tree

arch/arm64/include/asm/kvm_asm.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ enum __kvm_host_smccc_func {
6868
__KVM_HOST_SMCCC_FUNC___kvm_vcpu_run,
6969
__KVM_HOST_SMCCC_FUNC___kvm_flush_vm_context,
7070
__KVM_HOST_SMCCC_FUNC___kvm_tlb_flush_vmid_ipa,
71+
__KVM_HOST_SMCCC_FUNC___kvm_tlb_flush_vmid_ipa_nsh,
7172
__KVM_HOST_SMCCC_FUNC___kvm_tlb_flush_vmid,
7273
__KVM_HOST_SMCCC_FUNC___kvm_flush_cpu_context,
7374
__KVM_HOST_SMCCC_FUNC___kvm_timer_set_cntvoff,
@@ -225,6 +226,9 @@ extern void __kvm_flush_vm_context(void);
225226
extern void __kvm_flush_cpu_context(struct kvm_s2_mmu *mmu);
226227
extern void __kvm_tlb_flush_vmid_ipa(struct kvm_s2_mmu *mmu, phys_addr_t ipa,
227228
int level);
229+
extern void __kvm_tlb_flush_vmid_ipa_nsh(struct kvm_s2_mmu *mmu,
230+
phys_addr_t ipa,
231+
int level);
228232
extern void __kvm_tlb_flush_vmid(struct kvm_s2_mmu *mmu);
229233

230234
extern void __kvm_timer_set_cntvoff(u64 cntvoff);

arch/arm64/kvm/hyp/nvhe/hyp-main.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,15 @@ static void handle___kvm_tlb_flush_vmid_ipa(struct kvm_cpu_context *host_ctxt)
125125
__kvm_tlb_flush_vmid_ipa(kern_hyp_va(mmu), ipa, level);
126126
}
127127

128+
static void handle___kvm_tlb_flush_vmid_ipa_nsh(struct kvm_cpu_context *host_ctxt)
129+
{
130+
DECLARE_REG(struct kvm_s2_mmu *, mmu, host_ctxt, 1);
131+
DECLARE_REG(phys_addr_t, ipa, host_ctxt, 2);
132+
DECLARE_REG(int, level, host_ctxt, 3);
133+
134+
__kvm_tlb_flush_vmid_ipa_nsh(kern_hyp_va(mmu), ipa, level);
135+
}
136+
128137
static void handle___kvm_tlb_flush_vmid(struct kvm_cpu_context *host_ctxt)
129138
{
130139
DECLARE_REG(struct kvm_s2_mmu *, mmu, host_ctxt, 1);
@@ -315,6 +324,7 @@ static const hcall_t host_hcall[] = {
315324
HANDLE_FUNC(__kvm_vcpu_run),
316325
HANDLE_FUNC(__kvm_flush_vm_context),
317326
HANDLE_FUNC(__kvm_tlb_flush_vmid_ipa),
327+
HANDLE_FUNC(__kvm_tlb_flush_vmid_ipa_nsh),
318328
HANDLE_FUNC(__kvm_tlb_flush_vmid),
319329
HANDLE_FUNC(__kvm_flush_cpu_context),
320330
HANDLE_FUNC(__kvm_timer_set_cntvoff),

arch/arm64/kvm/hyp/nvhe/tlb.c

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,58 @@ void __kvm_tlb_flush_vmid_ipa(struct kvm_s2_mmu *mmu,
130130
__tlb_switch_to_host(&cxt);
131131
}
132132

133+
void __kvm_tlb_flush_vmid_ipa_nsh(struct kvm_s2_mmu *mmu,
134+
phys_addr_t ipa, int level)
135+
{
136+
struct tlb_inv_context cxt;
137+
138+
/* Switch to requested VMID */
139+
__tlb_switch_to_guest(mmu, &cxt, true);
140+
141+
/*
142+
* We could do so much better if we had the VA as well.
143+
* Instead, we invalidate Stage-2 for this IPA, and the
144+
* whole of Stage-1. Weep...
145+
*/
146+
ipa >>= 12;
147+
__tlbi_level(ipas2e1, ipa, level);
148+
149+
/*
150+
* We have to ensure completion of the invalidation at Stage-2,
151+
* since a table walk on another CPU could refill a TLB with a
152+
* complete (S1 + S2) walk based on the old Stage-2 mapping if
153+
* the Stage-1 invalidation happened first.
154+
*/
155+
dsb(nsh);
156+
__tlbi(vmalle1);
157+
dsb(nsh);
158+
isb();
159+
160+
/*
161+
* If the host is running at EL1 and we have a VPIPT I-cache,
162+
* then we must perform I-cache maintenance at EL2 in order for
163+
* it to have an effect on the guest. Since the guest cannot hit
164+
* I-cache lines allocated with a different VMID, we don't need
165+
* to worry about junk out of guest reset (we nuke the I-cache on
166+
* VMID rollover), but we do need to be careful when remapping
167+
* executable pages for the same guest. This can happen when KSM
168+
* takes a CoW fault on an executable page, copies the page into
169+
* a page that was previously mapped in the guest and then needs
170+
* to invalidate the guest view of the I-cache for that page
171+
* from EL1. To solve this, we invalidate the entire I-cache when
172+
* unmapping a page from a guest if we have a VPIPT I-cache but
173+
* the host is running at EL1. As above, we could do better if
174+
* we had the VA.
175+
*
176+
* The moral of this story is: if you have a VPIPT I-cache, then
177+
* you should be running with VHE enabled.
178+
*/
179+
if (icache_is_vpipt())
180+
icache_inval_all_pou();
181+
182+
__tlb_switch_to_host(&cxt);
183+
}
184+
133185
void __kvm_tlb_flush_vmid(struct kvm_s2_mmu *mmu)
134186
{
135187
struct tlb_inv_context cxt;

arch/arm64/kvm/hyp/pgtable.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1189,7 +1189,7 @@ int kvm_pgtable_stage2_relax_perms(struct kvm_pgtable *pgt, u64 addr,
11891189
KVM_PGTABLE_WALK_HANDLE_FAULT |
11901190
KVM_PGTABLE_WALK_SHARED);
11911191
if (!ret)
1192-
kvm_call_hyp(__kvm_tlb_flush_vmid_ipa, pgt->mmu, addr, level);
1192+
kvm_call_hyp(__kvm_tlb_flush_vmid_ipa_nsh, pgt->mmu, addr, level);
11931193
return ret;
11941194
}
11951195

arch/arm64/kvm/hyp/vhe/tlb.c

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,38 @@ void __kvm_tlb_flush_vmid_ipa(struct kvm_s2_mmu *mmu,
111111
__tlb_switch_to_host(&cxt);
112112
}
113113

114+
void __kvm_tlb_flush_vmid_ipa_nsh(struct kvm_s2_mmu *mmu,
115+
phys_addr_t ipa, int level)
116+
{
117+
struct tlb_inv_context cxt;
118+
119+
dsb(nshst);
120+
121+
/* Switch to requested VMID */
122+
__tlb_switch_to_guest(mmu, &cxt);
123+
124+
/*
125+
* We could do so much better if we had the VA as well.
126+
* Instead, we invalidate Stage-2 for this IPA, and the
127+
* whole of Stage-1. Weep...
128+
*/
129+
ipa >>= 12;
130+
__tlbi_level(ipas2e1, ipa, level);
131+
132+
/*
133+
* We have to ensure completion of the invalidation at Stage-2,
134+
* since a table walk on another CPU could refill a TLB with a
135+
* complete (S1 + S2) walk based on the old Stage-2 mapping if
136+
* the Stage-1 invalidation happened first.
137+
*/
138+
dsb(nsh);
139+
__tlbi(vmalle1);
140+
dsb(nsh);
141+
isb();
142+
143+
__tlb_switch_to_host(&cxt);
144+
}
145+
114146
void __kvm_tlb_flush_vmid(struct kvm_s2_mmu *mmu)
115147
{
116148
struct tlb_inv_context cxt;

0 commit comments

Comments
 (0)