Skip to content

Commit e6b673b

Browse files
Dave MartinMarc Zyngier
authored andcommitted
KVM: arm64: Optimise FPSIMD handling to reduce guest/host thrashing
This patch refactors KVM to align the host and guest FPSIMD save/restore logic with each other for arm64. This reduces the number of redundant save/restore operations that must occur, and reduces the common-case IRQ blackout time during guest exit storms by saving the host state lazily and optimising away the need to restore the host state before returning to the run loop. Four hooks are defined in order to enable this: * kvm_arch_vcpu_run_map_fp(): Called on PID change to map necessary bits of current to Hyp. * kvm_arch_vcpu_load_fp(): Set up FP/SIMD for entering the KVM run loop (parse as "vcpu_load fp"). * kvm_arch_vcpu_ctxsync_fp(): Get FP/SIMD into a safe state for re-enabling interrupts after a guest exit back to the run loop. For arm64 specifically, this involves updating the host kernel's FPSIMD context tracking metadata so that kernel-mode NEON use will cause the vcpu's FPSIMD state to be saved back correctly into the vcpu struct. This must be done before re-enabling interrupts because kernel-mode NEON may be used by softirqs. * kvm_arch_vcpu_put_fp(): Save guest FP/SIMD state back to memory and dissociate from the CPU ("vcpu_put fp"). Also, the arm64 FPSIMD context switch code is updated to enable it to save back FPSIMD state for a vcpu, not just current. A few helpers drive this: * fpsimd_bind_state_to_cpu(struct user_fpsimd_state *fp): mark this CPU as having context fp (which may belong to a vcpu) currently loaded in its registers. This is the non-task equivalent of the static function fpsimd_bind_to_cpu() in fpsimd.c. * task_fpsimd_save(): exported to allow KVM to save the guest's FPSIMD state back to memory on exit from the run loop. * fpsimd_flush_state(): invalidate any context's FPSIMD state that is currently loaded. Used to disassociate the vcpu from the CPU regs on run loop exit. These changes allow the run loop to enable interrupts (and thus softirqs that may use kernel-mode NEON) without having to save the guest's FPSIMD state eagerly. Some new vcpu_arch fields are added to make all this work. Because host FPSIMD state can now be saved back directly into current's thread_struct as appropriate, host_cpu_context is no longer used for preserving the FPSIMD state. However, it is still needed for preserving other things such as the host's system registers. To avoid ABI churn, the redundant storage space in host_cpu_context is not removed for now. arch/arm is not addressed by this patch and continues to use its current save/restore logic. It could provide implementations of the helpers later if desired. Signed-off-by: Dave Martin <[email protected]> Reviewed-by: Marc Zyngier <[email protected]> Reviewed-by: Christoffer Dall <[email protected]> Reviewed-by: Alex Bennée <[email protected]> Acked-by: Catalin Marinas <[email protected]> Signed-off-by: Marc Zyngier <[email protected]>
1 parent fa89d31 commit e6b673b

File tree

9 files changed

+192
-31
lines changed

9 files changed

+192
-31
lines changed

arch/arm/include/asm/kvm_host.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,14 @@ int kvm_arm_vcpu_arch_get_attr(struct kvm_vcpu *vcpu,
303303
int kvm_arm_vcpu_arch_has_attr(struct kvm_vcpu *vcpu,
304304
struct kvm_device_attr *attr);
305305

306+
/*
307+
* VFP/NEON switching is all done by the hyp switch code, so no need to
308+
* coordinate with host context handling for this state:
309+
*/
310+
static inline void kvm_arch_vcpu_load_fp(struct kvm_vcpu *vcpu) {}
311+
static inline void kvm_arch_vcpu_ctxsync_fp(struct kvm_vcpu *vcpu) {}
312+
static inline void kvm_arch_vcpu_put_fp(struct kvm_vcpu *vcpu) {}
313+
306314
/* All host FP/SIMD state is restored on guest exit, so nothing to save: */
307315
static inline void kvm_fpsimd_flush_cpu_state(void) {}
308316

arch/arm64/include/asm/fpsimd.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ struct task_struct;
4141
extern void fpsimd_save_state(struct user_fpsimd_state *state);
4242
extern void fpsimd_load_state(struct user_fpsimd_state *state);
4343

44+
extern void fpsimd_save(void);
45+
4446
extern void fpsimd_thread_switch(struct task_struct *next);
4547
extern void fpsimd_flush_thread(void);
4648

@@ -49,7 +51,11 @@ extern void fpsimd_preserve_current_state(void);
4951
extern void fpsimd_restore_current_state(void);
5052
extern void fpsimd_update_current_state(struct user_fpsimd_state const *state);
5153

54+
extern void fpsimd_bind_task_to_cpu(void);
55+
extern void fpsimd_bind_state_to_cpu(struct user_fpsimd_state *state);
56+
5257
extern void fpsimd_flush_task_state(struct task_struct *target);
58+
extern void fpsimd_flush_cpu_state(void);
5359
extern void sve_flush_cpu_state(void);
5460

5561
/* Maximum VL that SVE VL-agnostic software can transparently support */

arch/arm64/include/asm/kvm_host.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include <asm/kvm.h>
3131
#include <asm/kvm_asm.h>
3232
#include <asm/kvm_mmio.h>
33+
#include <asm/thread_info.h>
3334

3435
#define __KVM_HAVE_ARCH_INTC_INITIALIZED
3536

@@ -238,6 +239,10 @@ struct kvm_vcpu_arch {
238239

239240
/* Pointer to host CPU context */
240241
kvm_cpu_context_t *host_cpu_context;
242+
243+
struct thread_info *host_thread_info; /* hyp VA */
244+
struct user_fpsimd_state *host_fpsimd_state; /* hyp VA */
245+
241246
struct {
242247
/* {Break,watch}point registers */
243248
struct kvm_guest_debug_arch regs;
@@ -295,6 +300,9 @@ struct kvm_vcpu_arch {
295300

296301
/* vcpu_arch flags field values: */
297302
#define KVM_ARM64_DEBUG_DIRTY (1 << 0)
303+
#define KVM_ARM64_FP_ENABLED (1 << 1) /* guest FP regs loaded */
304+
#define KVM_ARM64_FP_HOST (1 << 2) /* host FP regs loaded */
305+
#define KVM_ARM64_HOST_SVE_IN_USE (1 << 3) /* backup for host TIF_SVE */
298306

299307
#define vcpu_gp_regs(v) (&(v)->arch.ctxt.gp_regs)
300308

@@ -423,6 +431,19 @@ static inline void __cpu_init_stage2(void)
423431
"PARange is %d bits, unsupported configuration!", parange);
424432
}
425433

434+
/* Guest/host FPSIMD coordination helpers */
435+
int kvm_arch_vcpu_run_map_fp(struct kvm_vcpu *vcpu);
436+
void kvm_arch_vcpu_load_fp(struct kvm_vcpu *vcpu);
437+
void kvm_arch_vcpu_ctxsync_fp(struct kvm_vcpu *vcpu);
438+
void kvm_arch_vcpu_put_fp(struct kvm_vcpu *vcpu);
439+
440+
#ifdef CONFIG_KVM /* Avoid conflicts with core headers if CONFIG_KVM=n */
441+
static inline int kvm_arch_vcpu_run_pid_change(struct kvm_vcpu *vcpu)
442+
{
443+
return kvm_arch_vcpu_run_map_fp(vcpu);
444+
}
445+
#endif
446+
426447
/*
427448
* All host FP/SIMD state is restored on guest exit, so nothing needs
428449
* doing here except in the SVE case:

arch/arm64/kernel/fpsimd.c

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -265,10 +265,10 @@ static void task_fpsimd_load(void)
265265
*
266266
* Softirqs (and preemption) must be disabled.
267267
*/
268-
static void fpsimd_save(void)
268+
void fpsimd_save(void)
269269
{
270270
struct user_fpsimd_state *st = __this_cpu_read(fpsimd_last_state.st);
271-
/* set by fpsimd_bind_task_to_cpu() */
271+
/* set by fpsimd_bind_task_to_cpu() or fpsimd_bind_state_to_cpu() */
272272

273273
WARN_ON(!in_softirq() && !irqs_disabled());
274274

@@ -986,7 +986,7 @@ void fpsimd_signal_preserve_current_state(void)
986986
* Associate current's FPSIMD context with this cpu
987987
* Preemption must be disabled when calling this function.
988988
*/
989-
static void fpsimd_bind_task_to_cpu(void)
989+
void fpsimd_bind_task_to_cpu(void)
990990
{
991991
struct fpsimd_last_state_struct *last =
992992
this_cpu_ptr(&fpsimd_last_state);
@@ -1006,6 +1006,17 @@ static void fpsimd_bind_task_to_cpu(void)
10061006
}
10071007
}
10081008

1009+
void fpsimd_bind_state_to_cpu(struct user_fpsimd_state *st)
1010+
{
1011+
struct fpsimd_last_state_struct *last =
1012+
this_cpu_ptr(&fpsimd_last_state);
1013+
1014+
WARN_ON(!in_softirq() && !irqs_disabled());
1015+
1016+
last->st = st;
1017+
last->sve_in_use = false;
1018+
}
1019+
10091020
/*
10101021
* Load the userland FPSIMD state of 'current' from memory, but only if the
10111022
* FPSIMD state already held in the registers is /not/ the most recent FPSIMD
@@ -1058,7 +1069,7 @@ void fpsimd_flush_task_state(struct task_struct *t)
10581069
t->thread.fpsimd_cpu = NR_CPUS;
10591070
}
10601071

1061-
static inline void fpsimd_flush_cpu_state(void)
1072+
void fpsimd_flush_cpu_state(void)
10621073
{
10631074
__this_cpu_write(fpsimd_last_state.st, NULL);
10641075
set_thread_flag(TIF_FOREIGN_FPSTATE);

arch/arm64/kvm/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ config KVM
3939
select HAVE_KVM_IRQ_ROUTING
4040
select IRQ_BYPASS_MANAGER
4141
select HAVE_KVM_IRQ_BYPASS
42+
select HAVE_KVM_VCPU_RUN_PID_CHANGE
4243
---help---
4344
Support hosting virtualized guest machines.
4445
We don't support KVM with 16K page tables yet, due to the multiple

arch/arm64/kvm/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ kvm-$(CONFIG_KVM_ARM_HOST) += $(KVM)/arm/psci.o $(KVM)/arm/perf.o
1919
kvm-$(CONFIG_KVM_ARM_HOST) += inject_fault.o regmap.o va_layout.o
2020
kvm-$(CONFIG_KVM_ARM_HOST) += hyp.o hyp-init.o handle_exit.o
2121
kvm-$(CONFIG_KVM_ARM_HOST) += guest.o debug.o reset.o sys_regs.o sys_regs_generic_v8.o
22-
kvm-$(CONFIG_KVM_ARM_HOST) += vgic-sys-reg-v3.o
22+
kvm-$(CONFIG_KVM_ARM_HOST) += vgic-sys-reg-v3.o fpsimd.o
2323
kvm-$(CONFIG_KVM_ARM_HOST) += $(KVM)/arm/aarch32.o
2424

2525
kvm-$(CONFIG_KVM_ARM_HOST) += $(KVM)/arm/vgic/vgic.o

arch/arm64/kvm/fpsimd.c

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* arch/arm64/kvm/fpsimd.c: Guest/host FPSIMD context coordination helpers
4+
*
5+
* Copyright 2018 Arm Limited
6+
* Author: Dave Martin <[email protected]>
7+
*/
8+
#include <linux/bottom_half.h>
9+
#include <linux/sched.h>
10+
#include <linux/thread_info.h>
11+
#include <linux/kvm_host.h>
12+
#include <asm/kvm_asm.h>
13+
#include <asm/kvm_host.h>
14+
#include <asm/kvm_mmu.h>
15+
16+
/*
17+
* Called on entry to KVM_RUN unless this vcpu previously ran at least
18+
* once and the most recent prior KVM_RUN for this vcpu was called from
19+
* the same task as current (highly likely).
20+
*
21+
* This is guaranteed to execute before kvm_arch_vcpu_load_fp(vcpu),
22+
* such that on entering hyp the relevant parts of current are already
23+
* mapped.
24+
*/
25+
int kvm_arch_vcpu_run_map_fp(struct kvm_vcpu *vcpu)
26+
{
27+
int ret;
28+
29+
struct thread_info *ti = &current->thread_info;
30+
struct user_fpsimd_state *fpsimd = &current->thread.uw.fpsimd_state;
31+
32+
/*
33+
* Make sure the host task thread flags and fpsimd state are
34+
* visible to hyp:
35+
*/
36+
ret = create_hyp_mappings(ti, ti + 1, PAGE_HYP);
37+
if (ret)
38+
goto error;
39+
40+
ret = create_hyp_mappings(fpsimd, fpsimd + 1, PAGE_HYP);
41+
if (ret)
42+
goto error;
43+
44+
vcpu->arch.host_thread_info = kern_hyp_va(ti);
45+
vcpu->arch.host_fpsimd_state = kern_hyp_va(fpsimd);
46+
error:
47+
return ret;
48+
}
49+
50+
/*
51+
* Prepare vcpu for saving the host's FPSIMD state and loading the guest's.
52+
* The actual loading is done by the FPSIMD access trap taken to hyp.
53+
*
54+
* Here, we just set the correct metadata to indicate that the FPSIMD
55+
* state in the cpu regs (if any) belongs to current on the host.
56+
*
57+
* TIF_SVE is backed up here, since it may get clobbered with guest state.
58+
* This flag is restored by kvm_arch_vcpu_put_fp(vcpu).
59+
*/
60+
void kvm_arch_vcpu_load_fp(struct kvm_vcpu *vcpu)
61+
{
62+
BUG_ON(system_supports_sve());
63+
BUG_ON(!current->mm);
64+
65+
vcpu->arch.flags &= ~(KVM_ARM64_FP_ENABLED | KVM_ARM64_HOST_SVE_IN_USE);
66+
vcpu->arch.flags |= KVM_ARM64_FP_HOST;
67+
if (test_thread_flag(TIF_SVE))
68+
vcpu->arch.flags |= KVM_ARM64_HOST_SVE_IN_USE;
69+
}
70+
71+
/*
72+
* If the guest FPSIMD state was loaded, update the host's context
73+
* tracking data mark the CPU FPSIMD regs as dirty and belonging to vcpu
74+
* so that they will be written back if the kernel clobbers them due to
75+
* kernel-mode NEON before re-entry into the guest.
76+
*/
77+
void kvm_arch_vcpu_ctxsync_fp(struct kvm_vcpu *vcpu)
78+
{
79+
WARN_ON_ONCE(!irqs_disabled());
80+
81+
if (vcpu->arch.flags & KVM_ARM64_FP_ENABLED) {
82+
fpsimd_bind_state_to_cpu(&vcpu->arch.ctxt.gp_regs.fp_regs);
83+
clear_thread_flag(TIF_FOREIGN_FPSTATE);
84+
clear_thread_flag(TIF_SVE);
85+
}
86+
}
87+
88+
/*
89+
* Write back the vcpu FPSIMD regs if they are dirty, and invalidate the
90+
* cpu FPSIMD regs so that they can't be spuriously reused if this vcpu
91+
* disappears and another task or vcpu appears that recycles the same
92+
* struct fpsimd_state.
93+
*/
94+
void kvm_arch_vcpu_put_fp(struct kvm_vcpu *vcpu)
95+
{
96+
local_bh_disable();
97+
98+
update_thread_flag(TIF_SVE,
99+
vcpu->arch.flags & KVM_ARM64_HOST_SVE_IN_USE);
100+
101+
if (vcpu->arch.flags & KVM_ARM64_FP_ENABLED) {
102+
/* Clean guest FP state to memory and invalidate cpu view */
103+
fpsimd_save();
104+
fpsimd_flush_cpu_state();
105+
} else if (!test_thread_flag(TIF_FOREIGN_FPSTATE)) {
106+
/* Ensure user trap controls are correctly restored */
107+
fpsimd_bind_task_to_cpu();
108+
}
109+
110+
local_bh_enable();
111+
}

0 commit comments

Comments
 (0)