Commit e7ef6ed4 authored by Marc Zyngier's avatar Marc Zyngier Committed by Oliver Upton
Browse files

KVM: arm64: Enforce NV limits on a per-idregs basis



As we are about to change the way the idreg reset values are computed,
move all the NV limits into a function that initialises one register
at a time.

This will be most useful in the upcoming patches. We take this opportunity
to remove the NV_FTR() macro and rely on the generated names instead.

Signed-off-by: default avatarMarc Zyngier <maz@kernel.org>
Reviewed-by: default avatarJoey Gouly <joey.gouly@arm.com>
Link: https://lore.kernel.org/r/20250220134907.554085-9-maz@kernel.org


Signed-off-by: default avatarOliver Upton <oliver.upton@linux.dev>
parent 179fd7e3
Loading
Loading
Loading
Loading
+136 −103
Original line number Diff line number Diff line
@@ -16,9 +16,6 @@

#include "sys_regs.h"

/* Protection against the sysreg repainting madness... */
#define NV_FTR(r, f)		ID_AA64##r##_EL1_##f

/*
 * Ratio of live shadow S2 MMU per vcpu. This is a trade-off between
 * memory usage and potential number of different sets of S2 PTs in
@@ -807,133 +804,169 @@ void kvm_arch_flush_shadow_all(struct kvm *kvm)
 * This list should get updated as new features get added to the NV
 * support, and new extension to the architecture.
 */
static void limit_nv_id_regs(struct kvm *kvm)
static u64 limit_nv_id_reg(struct kvm *kvm, u32 reg, u64 val)
{
	u64 val, tmp;

	switch (reg) {
	case SYS_ID_AA64ISAR0_EL1:
		/* Support everything but TME */
	val = kvm_read_vm_id_reg(kvm, SYS_ID_AA64ISAR0_EL1);
	val &= ~NV_FTR(ISAR0, TME);
	kvm_set_vm_id_reg(kvm, SYS_ID_AA64ISAR0_EL1, val);
		val &= ~ID_AA64ISAR0_EL1_TME;
		break;

	/* Support everything but Spec Invalidation and LS64 */
	val = kvm_read_vm_id_reg(kvm, SYS_ID_AA64ISAR1_EL1);
	val &= ~(NV_FTR(ISAR1, LS64)	|
		 NV_FTR(ISAR1, SPECRES));
	kvm_set_vm_id_reg(kvm, SYS_ID_AA64ISAR1_EL1, val);
	case SYS_ID_AA64ISAR1_EL1:
		/* Support everything but LS64 and Spec Invalidation */
		val &= ~(ID_AA64ISAR1_EL1_LS64	|
			 ID_AA64ISAR1_EL1_SPECRES);
		break;

	/* No AMU, MPAM, S-EL2, or RAS */
	val = kvm_read_vm_id_reg(kvm, SYS_ID_AA64PFR0_EL1);
	val &= ~(GENMASK_ULL(55, 52)	|
		 NV_FTR(PFR0, AMU)	|
		 NV_FTR(PFR0, MPAM)	|
		 NV_FTR(PFR0, SEL2)	|
		 NV_FTR(PFR0, RAS)	|
		 NV_FTR(PFR0, EL3)	|
		 NV_FTR(PFR0, EL2)	|
		 NV_FTR(PFR0, EL1)	|
		 NV_FTR(PFR0, EL0));
	case SYS_ID_AA64PFR0_EL1:
		/* No RME, AMU, MPAM, S-EL2, or RAS */
		val &= ~(ID_AA64PFR0_EL1_RME	|
			 ID_AA64PFR0_EL1_AMU	|
			 ID_AA64PFR0_EL1_MPAM	|
			 ID_AA64PFR0_EL1_SEL2	|
			 ID_AA64PFR0_EL1_RAS	|
			 ID_AA64PFR0_EL1_EL3	|
			 ID_AA64PFR0_EL1_EL2	|
			 ID_AA64PFR0_EL1_EL1	|
			 ID_AA64PFR0_EL1_EL0);
		/* 64bit only at any EL */
	val |= FIELD_PREP(NV_FTR(PFR0, EL0), 0b0001);
	val |= FIELD_PREP(NV_FTR(PFR0, EL1), 0b0001);
	val |= FIELD_PREP(NV_FTR(PFR0, EL2), 0b0001);
	val |= FIELD_PREP(NV_FTR(PFR0, EL3), 0b0001);
	kvm_set_vm_id_reg(kvm, SYS_ID_AA64PFR0_EL1, val);
		val |= SYS_FIELD_PREP_ENUM(ID_AA64PFR0_EL1, EL0, IMP);
		val |= SYS_FIELD_PREP_ENUM(ID_AA64PFR0_EL1, EL1, IMP);
		val |= SYS_FIELD_PREP_ENUM(ID_AA64PFR0_EL1, EL2, IMP);
		val |= SYS_FIELD_PREP_ENUM(ID_AA64PFR0_EL1, EL3, IMP);
		break;

	case SYS_ID_AA64PFR1_EL1:
		/* Only support BTI, SSBS, CSV2_frac */
	val = kvm_read_vm_id_reg(kvm, SYS_ID_AA64PFR1_EL1);
	val &= (NV_FTR(PFR1, BT)	|
		NV_FTR(PFR1, SSBS)	|
		NV_FTR(PFR1, CSV2_frac));
	kvm_set_vm_id_reg(kvm, SYS_ID_AA64PFR1_EL1, val);
		val &= (ID_AA64PFR1_EL1_BT	|
			ID_AA64PFR1_EL1_SSBS	|
			ID_AA64PFR1_EL1_CSV2_frac);
		break;

	case SYS_ID_AA64MMFR0_EL1:
		/* Hide ECV, ExS, Secure Memory */
	val = kvm_read_vm_id_reg(kvm, SYS_ID_AA64MMFR0_EL1);
	val &= ~(NV_FTR(MMFR0, ECV)		|
		 NV_FTR(MMFR0, EXS)		|
		 NV_FTR(MMFR0, TGRAN4_2)	|
		 NV_FTR(MMFR0, TGRAN16_2)	|
		 NV_FTR(MMFR0, TGRAN64_2)	|
		 NV_FTR(MMFR0, SNSMEM));
		val &= ~(ID_AA64MMFR0_EL1_ECV		|
			 ID_AA64MMFR0_EL1_EXS		|
			 ID_AA64MMFR0_EL1_TGRAN4_2	|
			 ID_AA64MMFR0_EL1_TGRAN16_2	|
			 ID_AA64MMFR0_EL1_TGRAN64_2	|
			 ID_AA64MMFR0_EL1_SNSMEM);

		/* Disallow unsupported S2 page sizes */
		switch (PAGE_SIZE) {
		case SZ_64K:
		val |= FIELD_PREP(NV_FTR(MMFR0, TGRAN16_2), 0b0001);
			val |= SYS_FIELD_PREP_ENUM(ID_AA64MMFR0_EL1, TGRAN16_2, NI);
			fallthrough;
		case SZ_16K:
		val |= FIELD_PREP(NV_FTR(MMFR0, TGRAN4_2), 0b0001);
			val |= SYS_FIELD_PREP_ENUM(ID_AA64MMFR0_EL1, TGRAN4_2, NI);
			fallthrough;
		case SZ_4K:
			/* Support everything */
			break;
		}

		/*
	 * Since we can't support a guest S2 page size smaller than
	 * the host's own page size (due to KVM only populating its
	 * own S2 using the kernel's page size), advertise the
	 * limitation using FEAT_GTG.
		 * Since we can't support a guest S2 page size smaller
		 * than the host's own page size (due to KVM only
		 * populating its own S2 using the kernel's page
		 * size), advertise the limitation using FEAT_GTG.
		 */
		switch (PAGE_SIZE) {
		case SZ_4K:
		val |= FIELD_PREP(NV_FTR(MMFR0, TGRAN4_2), 0b0010);
			val |= SYS_FIELD_PREP_ENUM(ID_AA64MMFR0_EL1, TGRAN4_2, IMP);
			fallthrough;
		case SZ_16K:
		val |= FIELD_PREP(NV_FTR(MMFR0, TGRAN16_2), 0b0010);
			val |= SYS_FIELD_PREP_ENUM(ID_AA64MMFR0_EL1, TGRAN16_2, IMP);
			fallthrough;
		case SZ_64K:
		val |= FIELD_PREP(NV_FTR(MMFR0, TGRAN64_2), 0b0010);
			val |= SYS_FIELD_PREP_ENUM(ID_AA64MMFR0_EL1, TGRAN64_2, IMP);
			break;
		}

		/* Cap PARange to 48bits */
	tmp = FIELD_GET(NV_FTR(MMFR0, PARANGE), val);
	if (tmp > 0b0101) {
		val &= ~NV_FTR(MMFR0, PARANGE);
		val |= FIELD_PREP(NV_FTR(MMFR0, PARANGE), 0b0101);
		val = ID_REG_LIMIT_FIELD_ENUM(val, ID_AA64MMFR0_EL1, PARANGE, 48);
		break;

	case SYS_ID_AA64MMFR1_EL1:
		val &= (ID_AA64MMFR1_EL1_HCX	|
			ID_AA64MMFR1_EL1_PAN	|
			ID_AA64MMFR1_EL1_LO	|
			ID_AA64MMFR1_EL1_HPDS	|
			ID_AA64MMFR1_EL1_VH	|
			ID_AA64MMFR1_EL1_VMIDBits);
		break;

	case SYS_ID_AA64MMFR2_EL1:
		val &= ~(ID_AA64MMFR2_EL1_BBM	|
			 ID_AA64MMFR2_EL1_TTL	|
			 GENMASK_ULL(47, 44)	|
			 ID_AA64MMFR2_EL1_ST	|
			 ID_AA64MMFR2_EL1_CCIDX	|
			 ID_AA64MMFR2_EL1_VARange);

		/* Force TTL support */
		val |= SYS_FIELD_PREP_ENUM(ID_AA64MMFR2_EL1, TTL, IMP);
		break;

	case SYS_ID_AA64MMFR4_EL1:
		val = SYS_FIELD_PREP_ENUM(ID_AA64MMFR4_EL1, NV_frac, NV2_ONLY);
		val |= SYS_FIELD_PREP_ENUM(ID_AA64MMFR4_EL1, E2H0, NI_NV1);
		break;

	case SYS_ID_AA64DFR0_EL1:
		/* Only limited support for PMU, Debug, BPs, WPs, and HPMN0 */
		val &= (ID_AA64DFR0_EL1_PMUVer	|
			ID_AA64DFR0_EL1_WRPs	|
			ID_AA64DFR0_EL1_BRPs	|
			ID_AA64DFR0_EL1_DebugVer|
			ID_AA64DFR0_EL1_HPMN0);

		/* Cap Debug to ARMv8.1 */
		val = ID_REG_LIMIT_FIELD_ENUM(val, ID_AA64DFR0_EL1, DebugVer, VHE);
		break;
	}

	return val;
}

static void limit_nv_id_regs(struct kvm *kvm)
{
	u64 val;

	val = kvm_read_vm_id_reg(kvm, SYS_ID_AA64ISAR0_EL1);
	val = limit_nv_id_reg(kvm, SYS_ID_AA64ISAR0_EL1, val);
	kvm_set_vm_id_reg(kvm, SYS_ID_AA64ISAR0_EL1, val);

	val = kvm_read_vm_id_reg(kvm, SYS_ID_AA64ISAR1_EL1);
	val = limit_nv_id_reg(kvm, SYS_ID_AA64ISAR1_EL1, val);
	kvm_set_vm_id_reg(kvm, SYS_ID_AA64ISAR1_EL1, val);

	val = kvm_read_vm_id_reg(kvm, SYS_ID_AA64PFR0_EL1);
	val = limit_nv_id_reg(kvm, SYS_ID_AA64PFR0_EL1, val);
	kvm_set_vm_id_reg(kvm, SYS_ID_AA64PFR0_EL1, val);

	val = kvm_read_vm_id_reg(kvm, SYS_ID_AA64PFR1_EL1);
	val = limit_nv_id_reg(kvm, SYS_ID_AA64PFR1_EL1, val);
	kvm_set_vm_id_reg(kvm, SYS_ID_AA64PFR1_EL1, val);

	val = kvm_read_vm_id_reg(kvm, SYS_ID_AA64MMFR0_EL1);
	val = limit_nv_id_reg(kvm, SYS_ID_AA64MMFR0_EL1, val);
	kvm_set_vm_id_reg(kvm, SYS_ID_AA64MMFR0_EL1, val);

	val = kvm_read_vm_id_reg(kvm, SYS_ID_AA64MMFR1_EL1);
	val &= (NV_FTR(MMFR1, HCX)	|
		NV_FTR(MMFR1, PAN)	|
		NV_FTR(MMFR1, LO)	|
		NV_FTR(MMFR1, HPDS)	|
		NV_FTR(MMFR1, VH)	|
		NV_FTR(MMFR1, VMIDBits));
	val = limit_nv_id_reg(kvm, SYS_ID_AA64MMFR1_EL1, val);
	kvm_set_vm_id_reg(kvm, SYS_ID_AA64MMFR1_EL1, val);

	val = kvm_read_vm_id_reg(kvm, SYS_ID_AA64MMFR2_EL1);
	val &= ~(NV_FTR(MMFR2, BBM)	|
		 NV_FTR(MMFR2, TTL)	|
		 GENMASK_ULL(47, 44)	|
		 NV_FTR(MMFR2, ST)	|
		 NV_FTR(MMFR2, CCIDX)	|
		 NV_FTR(MMFR2, VARange));

	/* Force TTL support */
	val |= FIELD_PREP(NV_FTR(MMFR2, TTL), 0b0001);
	val = limit_nv_id_reg(kvm, SYS_ID_AA64MMFR2_EL1, val);
	kvm_set_vm_id_reg(kvm, SYS_ID_AA64MMFR2_EL1, val);

	val = 0;
	if (!cpus_have_final_cap(ARM64_HAS_HCR_NV1))
		val |= FIELD_PREP(NV_FTR(MMFR4, E2H0),
				  ID_AA64MMFR4_EL1_E2H0_NI_NV1);
	val = kvm_read_vm_id_reg(kvm, SYS_ID_AA64MMFR4_EL1);
	val = limit_nv_id_reg(kvm, SYS_ID_AA64MMFR4_EL1, val);
	kvm_set_vm_id_reg(kvm, SYS_ID_AA64MMFR4_EL1, val);

	/* Only limited support for PMU, Debug, BPs, WPs, and HPMN0 */
	val = kvm_read_vm_id_reg(kvm, SYS_ID_AA64DFR0_EL1);
	val &= (NV_FTR(DFR0, PMUVer)	|
		NV_FTR(DFR0, WRPs)	|
		NV_FTR(DFR0, BRPs)	|
		NV_FTR(DFR0, DebugVer)	|
		NV_FTR(DFR0, HPMN0));

	/* Cap Debug to ARMv8.1 */
	tmp = FIELD_GET(NV_FTR(DFR0, DebugVer), val);
	if (tmp > 0b0111) {
		val &= ~NV_FTR(DFR0, DebugVer);
		val |= FIELD_PREP(NV_FTR(DFR0, DebugVer), 0b0111);
	}
	val = limit_nv_id_reg(kvm, SYS_ID_AA64DFR0_EL1, val);
	kvm_set_vm_id_reg(kvm, SYS_ID_AA64DFR0_EL1, val);
}