Skip to content

Commit 5544750

Browse files
Marc Zyngieroupton
authored andcommitted
KVM: arm64: Build MPIDR to vcpu index cache at runtime
The MPIDR_EL1 register contains a unique value that identifies the CPU. The only problem with it is that it is stupidly large (32 bits, once the useless stuff is removed). Trying to obtain a vcpu from an MPIDR value is a fairly common, yet costly operation: we iterate over all the vcpus until we find the correct one. While this is cheap for small VMs, it is pretty expensive on large ones, specially if you are trying to get to the one that's at the end of the list... In order to help with this, it is important to realise that the MPIDR values are actually structured, and that implementations tend to use a small number of significant bits in the 32bit space. We can use this fact to our advantage by computing a small hash table that uses the "compression" of the significant MPIDR bits as an index, giving us the vcpu index as a result. Given that the MPIDR values can be supplied by userspace, and that an evil VMM could decide to make *all* bits significant, resulting in a 4G-entry table, we only use this method if the resulting table fits in a single page. Otherwise, we fallback to the good old iterative method. Nothing uses that table just yet, but keep your eyes peeled. Reviewed-by: Joey Gouly <[email protected]> Reviewed-by: Zenghui Yu <[email protected]> Tested-by: Joey Gouly <[email protected]> Tested-by: Shameer Kolothum <[email protected]> Signed-off-by: Marc Zyngier <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Oliver Upton <[email protected]>
1 parent 0a2acd3 commit 5544750

File tree

2 files changed

+82
-0
lines changed

2 files changed

+82
-0
lines changed

arch/arm64/include/asm/kvm_host.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,31 @@ struct kvm_protected_vm {
202202
struct kvm_hyp_memcache teardown_mc;
203203
};
204204

205+
struct kvm_mpidr_data {
206+
u64 mpidr_mask;
207+
DECLARE_FLEX_ARRAY(u16, cmpidr_to_idx);
208+
};
209+
210+
static inline u16 kvm_mpidr_index(struct kvm_mpidr_data *data, u64 mpidr)
211+
{
212+
unsigned long mask = data->mpidr_mask;
213+
u64 aff = mpidr & MPIDR_HWID_BITMASK;
214+
int nbits, bit, bit_idx = 0;
215+
u16 index = 0;
216+
217+
/*
218+
* If this looks like RISC-V's BEXT or x86's PEXT
219+
* instructions, it isn't by accident.
220+
*/
221+
nbits = fls(mask);
222+
for_each_set_bit(bit, &mask, nbits) {
223+
index |= (aff & BIT(bit)) >> (bit - bit_idx);
224+
bit_idx++;
225+
}
226+
227+
return index;
228+
}
229+
205230
struct kvm_arch {
206231
struct kvm_s2_mmu mmu;
207232

@@ -248,6 +273,9 @@ struct kvm_arch {
248273
/* VM-wide vCPU feature set */
249274
DECLARE_BITMAP(vcpu_features, KVM_VCPU_MAX_FEATURES);
250275

276+
/* MPIDR to vcpu index mapping, optional */
277+
struct kvm_mpidr_data *mpidr_data;
278+
251279
/*
252280
* VM-wide PMU filter, implemented as a bitmap and big enough for
253281
* up to 2^10 events (ARMv8.0) or 2^16 events (ARMv8.1+).

arch/arm64/kvm/arm.c

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ void kvm_arch_destroy_vm(struct kvm *kvm)
205205
if (is_protected_kvm_enabled())
206206
pkvm_destroy_hyp_vm(kvm);
207207

208+
kfree(kvm->arch.mpidr_data);
208209
kvm_destroy_vcpus(kvm);
209210

210211
kvm_unshare_hyp(kvm, kvm + 1);
@@ -578,6 +579,57 @@ static int kvm_vcpu_initialized(struct kvm_vcpu *vcpu)
578579
return vcpu_get_flag(vcpu, VCPU_INITIALIZED);
579580
}
580581

582+
static void kvm_init_mpidr_data(struct kvm *kvm)
583+
{
584+
struct kvm_mpidr_data *data = NULL;
585+
unsigned long c, mask, nr_entries;
586+
u64 aff_set = 0, aff_clr = ~0UL;
587+
struct kvm_vcpu *vcpu;
588+
589+
mutex_lock(&kvm->arch.config_lock);
590+
591+
if (kvm->arch.mpidr_data || atomic_read(&kvm->online_vcpus) == 1)
592+
goto out;
593+
594+
kvm_for_each_vcpu(c, vcpu, kvm) {
595+
u64 aff = kvm_vcpu_get_mpidr_aff(vcpu);
596+
aff_set |= aff;
597+
aff_clr &= aff;
598+
}
599+
600+
/*
601+
* A significant bit can be either 0 or 1, and will only appear in
602+
* aff_set. Use aff_clr to weed out the useless stuff.
603+
*/
604+
mask = aff_set ^ aff_clr;
605+
nr_entries = BIT_ULL(hweight_long(mask));
606+
607+
/*
608+
* Don't let userspace fool us. If we need more than a single page
609+
* to describe the compressed MPIDR array, just fall back to the
610+
* iterative method. Single vcpu VMs do not need this either.
611+
*/
612+
if (struct_size(data, cmpidr_to_idx, nr_entries) <= PAGE_SIZE)
613+
data = kzalloc(struct_size(data, cmpidr_to_idx, nr_entries),
614+
GFP_KERNEL_ACCOUNT);
615+
616+
if (!data)
617+
goto out;
618+
619+
data->mpidr_mask = mask;
620+
621+
kvm_for_each_vcpu(c, vcpu, kvm) {
622+
u64 aff = kvm_vcpu_get_mpidr_aff(vcpu);
623+
u16 index = kvm_mpidr_index(data, aff);
624+
625+
data->cmpidr_to_idx[index] = c;
626+
}
627+
628+
kvm->arch.mpidr_data = data;
629+
out:
630+
mutex_unlock(&kvm->arch.config_lock);
631+
}
632+
581633
/*
582634
* Handle both the initialisation that is being done when the vcpu is
583635
* run for the first time, as well as the updates that must be
@@ -601,6 +653,8 @@ int kvm_arch_vcpu_run_pid_change(struct kvm_vcpu *vcpu)
601653
if (likely(vcpu_has_run_once(vcpu)))
602654
return 0;
603655

656+
kvm_init_mpidr_data(kvm);
657+
604658
kvm_arm_vcpu_init_debug(vcpu);
605659

606660
if (likely(irqchip_in_kernel(kvm))) {

0 commit comments

Comments
 (0)