Commit 77ee70a0 authored by Oliver Upton's avatar Oliver Upton
Browse files

KVM: arm64: nv: Honor SError exception routing / masking



To date KVM has used HCR_EL2.VSE to track the state of a pending SError
for the guest. With this bit set, hardware respects the EL1 exception
routing / masking rules and injects the vSError when appropriate.

This isn't correct for NV guests as hardware is oblivious to vEL2's
intentions for SErrors. Better yet, with FEAT_NV2 the guest can change
the routing behind our back as HCR_EL2 is redirected to memory. Cope
with this mess by:

 - Using a flag (instead of HCR_EL2.VSE) to track the pending SError
   state when SErrors are unconditionally masked for the current context

 - Resampling the routing / masking of a pending SError on every guest
   entry/exit

 - Emulating exception entry when SError routing implies a translation
   regime change

Reviewed-by: default avatarMarc Zyngier <maz@kernel.org>
Link: https://lore.kernel.org/r/20250708172532.1699409-7-oliver.upton@linux.dev


Signed-off-by: default avatarOliver Upton <oliver.upton@linux.dev>
parent 9aba641b
Loading
Loading
Loading
Loading
+19 −1
Original line number Diff line number Diff line
@@ -45,7 +45,7 @@ bool kvm_condition_valid32(const struct kvm_vcpu *vcpu);
void kvm_skip_instr32(struct kvm_vcpu *vcpu);

void kvm_inject_undefined(struct kvm_vcpu *vcpu);
void kvm_inject_vabt(struct kvm_vcpu *vcpu);
int kvm_inject_serror_esr(struct kvm_vcpu *vcpu, u64 esr);
int kvm_inject_sea(struct kvm_vcpu *vcpu, bool iabt, u64 addr);
void kvm_inject_size_fault(struct kvm_vcpu *vcpu);

@@ -59,12 +59,25 @@ static inline int kvm_inject_sea_iabt(struct kvm_vcpu *vcpu, u64 addr)
	return kvm_inject_sea(vcpu, true, addr);
}

static inline int kvm_inject_serror(struct kvm_vcpu *vcpu)
{
	/*
	 * ESR_ELx.ISV (later renamed to IDS) indicates whether or not
	 * ESR_ELx.ISS contains IMPLEMENTATION DEFINED syndrome information.
	 *
	 * Set the bit when injecting an SError w/o an ESR to indicate ISS
	 * does not follow the architected format.
	 */
	return kvm_inject_serror_esr(vcpu, ESR_ELx_ISV);
}

void kvm_vcpu_wfi(struct kvm_vcpu *vcpu);

void kvm_emulate_nested_eret(struct kvm_vcpu *vcpu);
int kvm_inject_nested_sync(struct kvm_vcpu *vcpu, u64 esr_el2);
int kvm_inject_nested_irq(struct kvm_vcpu *vcpu);
int kvm_inject_nested_sea(struct kvm_vcpu *vcpu, bool iabt, u64 addr);
int kvm_inject_nested_serror(struct kvm_vcpu *vcpu, u64 esr);

static inline void kvm_inject_nested_sve_trap(struct kvm_vcpu *vcpu)
{
@@ -205,6 +218,11 @@ static inline bool vcpu_el2_tge_is_set(const struct kvm_vcpu *vcpu)
	return ctxt_sys_reg(&vcpu->arch.ctxt, HCR_EL2) & HCR_TGE;
}

static inline bool vcpu_el2_amo_is_set(const struct kvm_vcpu *vcpu)
{
	return ctxt_sys_reg(&vcpu->arch.ctxt, HCR_EL2) & HCR_AMO;
}

static inline bool is_hyp_ctxt(const struct kvm_vcpu *vcpu)
{
	bool e2h, tge;
+15 −3
Original line number Diff line number Diff line
@@ -817,7 +817,7 @@ struct kvm_vcpu_arch {
	u8 iflags;

	/* State flags for kernel bookkeeping, unused by the hypervisor code */
	u8 sflags;
	u16 sflags;

	/*
	 * Don't run the guest (internal implementation need).
@@ -953,9 +953,21 @@ struct kvm_vcpu_arch {
		__vcpu_flags_preempt_enable();			\
	} while (0)

#define __vcpu_test_and_clear_flag(v, flagset, f, m)		\
	({							\
		typeof(v->arch.flagset) set;			\
								\
		set = __vcpu_get_flag(v, flagset, f, m);	\
		__vcpu_clear_flag(v, flagset, f, m);		\
								\
		set;						\
	})

#define vcpu_get_flag(v, ...)	__vcpu_get_flag((v), __VA_ARGS__)
#define vcpu_set_flag(v, ...)	__vcpu_set_flag((v), __VA_ARGS__)
#define vcpu_clear_flag(v, ...)	__vcpu_clear_flag((v), __VA_ARGS__)
#define vcpu_test_and_clear_flag(v, ...)			\
	__vcpu_test_and_clear_flag((v), __VA_ARGS__)

/* KVM_ARM_VCPU_INIT completed */
#define VCPU_INITIALIZED	__vcpu_single_flag(cflags, BIT(0))
@@ -1015,6 +1027,8 @@ struct kvm_vcpu_arch {
#define IN_WFI			__vcpu_single_flag(sflags, BIT(6))
/* KVM is currently emulating a nested ERET */
#define IN_NESTED_ERET		__vcpu_single_flag(sflags, BIT(7))
/* SError pending for nested guest */
#define NESTED_SERROR_PENDING	__vcpu_single_flag(sflags, BIT(8))


/* Pointer to the vcpu's SVE FFR for sve_{save,load}_state() */
@@ -1387,8 +1401,6 @@ static inline bool kvm_arm_is_pvtime_enabled(struct kvm_vcpu_arch *vcpu_arch)
	return (vcpu_arch->steal.base != INVALID_GPA);
}

void kvm_set_sei_esr(struct kvm_vcpu *vcpu, u64 syndrome);

struct kvm_vcpu *kvm_mpidr_to_vcpu(struct kvm *kvm, unsigned long mpidr);

DECLARE_KVM_HYP_PER_CPU(struct kvm_host_data, kvm_host_data);
+2 −0
Original line number Diff line number Diff line
@@ -80,6 +80,8 @@ extern void kvm_vcpu_load_hw_mmu(struct kvm_vcpu *vcpu);
extern void kvm_vcpu_put_hw_mmu(struct kvm_vcpu *vcpu);

extern void check_nested_vcpu_requests(struct kvm_vcpu *vcpu);
extern void kvm_nested_flush_hwstate(struct kvm_vcpu *vcpu);
extern void kvm_nested_sync_hwstate(struct kvm_vcpu *vcpu);

struct kvm_s2_trans {
	phys_addr_t output;
+4 −0
Original line number Diff line number Diff line
@@ -1188,6 +1188,8 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu)
		 */
		preempt_disable();

		kvm_nested_flush_hwstate(vcpu);

		if (kvm_vcpu_has_pmu(vcpu))
			kvm_pmu_flush_hwstate(vcpu);

@@ -1287,6 +1289,8 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu)
		/* Exit types that need handling before we can be preempted */
		handle_exit_early(vcpu, ret);

		kvm_nested_sync_hwstate(vcpu);

		preempt_enable();

		/*
+14 −0
Original line number Diff line number Diff line
@@ -2714,6 +2714,9 @@ static void kvm_inject_el2_exception(struct kvm_vcpu *vcpu, u64 esr_el2,
	case except_type_irq:
		kvm_pend_exception(vcpu, EXCEPT_AA64_EL2_IRQ);
		break;
	case except_type_serror:
		kvm_pend_exception(vcpu, EXCEPT_AA64_EL2_SERR);
		break;
	default:
		WARN_ONCE(1, "Unsupported EL2 exception injection %d\n", type);
	}
@@ -2821,3 +2824,14 @@ int kvm_inject_nested_sea(struct kvm_vcpu *vcpu, bool iabt, u64 addr)
	vcpu_write_sys_reg(vcpu, FAR_EL2, addr);
	return kvm_inject_nested_sync(vcpu, esr);
}

int kvm_inject_nested_serror(struct kvm_vcpu *vcpu, u64 esr)
{
	/*
	 * Hardware sets up the EC field when propagating ESR as a result of
	 * vSError injection. Manually populate EC for an emulated SError
	 * exception.
	 */
	esr |= FIELD_PREP(ESR_ELx_EC_MASK, ESR_ELx_EC_SERROR);
	return kvm_inject_nested(vcpu, esr, except_type_serror);
}
Loading