[PATCH v1 3/3] KVM: arm64: Convert TCR_EL2 to config-driven sanitisation
From: Wei-Lin Chang
Date: Wed Sep 09 2026 - 18:22:40 EST
Feature dependencies of the fields in TCR_EL2 are extracted from
Registers.json of the BSD licenced AARCHMRS
(AARCHMRS_OPENSOURCE_A_profile_FAT-2026-06).
Places where we slightly deviate from the above:
- Stick to the ARM ARM M.c requirement for TCR_EL2.TCMA*, which only
depends on FEAT_MTE2. This avoids updating ID_AA64PFR2_EL1 with new
VMTE* definitions that we don't know the meaning of, as they aren't
present in the ARM ARM yet.
- Ignore TCR_EL2.DS's requirement of needing the effective value of
TCR2_EL2.D128 be 0. This requires runtime detection of TCR2_EL2.D128
and we don't have D128 support yet.
- TCR_EL2.AS is dependent on FEAT_ASID16, this information is missing
from the json.
Signed-off-by: Wei-Lin Chang <weilin.chang@xxxxxxx>
---
arch/arm64/include/asm/kvm_host.h | 2 +-
arch/arm64/kvm/config.c | 113 ++++++++++++++++++++++++++++++
arch/arm64/kvm/nested.c | 4 ++
3 files changed, 118 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index 27fe0cd5b2d7..6a5e22ca3430 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -516,7 +516,6 @@ enum vcpu_sysreg {
HACR_EL2, /* Hypervisor Auxiliary Control Register */
TTBR0_EL2, /* Translation Table Base Register 0 (EL2) */
TTBR1_EL2, /* Translation Table Base Register 1 (EL2) */
- TCR_EL2, /* Translation Control Register (EL2) */
PIRE0_EL2, /* Permission Indirection Register 0 (EL2) */
PIR_EL2, /* Permission Indirection Register 1 (EL2) */
POR_EL2, /* Permission Overlay Register 2 (EL2) */
@@ -541,6 +540,7 @@ enum vcpu_sysreg {
/* Anything from this can be RES0/RES1 sanitised */
MARKER(__SANITISED_REG_START__),
SCTLR_EL2, /* System Control Register (EL2) */
+ TCR_EL2, /* Translation Control Register (EL2) */
TCR2_EL2, /* Extended Translation Control Register (EL2) */
SCTLR2_EL2, /* System Control Register 2 (EL2) */
MDCR_EL2, /* Monitor Debug Configuration Register (EL2) */
diff --git a/arch/arm64/kvm/config.c b/arch/arm64/kvm/config.c
index 1053676551af..3ccf5359a373 100644
--- a/arch/arm64/kvm/config.c
+++ b/arch/arm64/kvm/config.c
@@ -203,8 +203,11 @@ struct reg_feat_map_desc {
#define FEAT_CPA2 ID_AA64ISAR3_EL1, CPA, CPA2
#define FEAT_ASID2 ID_AA64MMFR4_EL1, ASID2, IMP
#define FEAT_MEC ID_AA64MMFR3_EL1, MEC, IMP
+#define FEAT_HAF ID_AA64MMFR1_EL1, HAFDBS, AF
#define FEAT_HAFT ID_AA64MMFR1_EL1, HAFDBS, HAFT
+#define FEAT_HAFDBS ID_AA64MMFR1_EL1, HAFDBS, DBM
#define FEAT_HDBSS ID_AA64MMFR1_EL1, HAFDBS, HDBSS
+#define FEAT_HPDS ID_AA64MMFR1_EL1, HPDS, IMP
#define FEAT_HPDS2 ID_AA64MMFR1_EL1, HPDS, HPDS2
#define FEAT_BTI ID_AA64PFR1_EL1, BT, IMP
#define FEAT_ExS ID_AA64MMFR0_EL1, EXS, IMP
@@ -215,6 +218,10 @@ struct reg_feat_map_desc {
#define FEAT_MixedEndEL0 ID_AA64MMFR0_EL1, BIGENDEL0, IMP
#define FEAT_MTE_ASYNC ID_AA64PFR1_EL1, MTE_frac, ASYNC
#define FEAT_MTE_STORE_ONLY ID_AA64PFR2_EL1, MTESTOREONLY, IMP
+#define FEAT_MTE_CANONICAL_TAGS ID_AA64PFR1_EL1, MTEX, MTE4
+#define FEAT_MTE_NO_ADDRESS_TAGS ID_AA64PFR1_EL1, MTEX, MTE4
+#define FEAT_E0PD ID_AA64MMFR2_EL1, E0PD, IMP
+#define FEAT_SVE ID_AA64PFR0_EL1, SVE, IMP
#define FEAT_PAN ID_AA64MMFR1_EL1, PAN, IMP
#define FEAT_PAN3 ID_AA64MMFR1_EL1, PAN, PAN3
#define FEAT_SSBS ID_AA64PFR1_EL1, SSBS, IMP
@@ -227,6 +234,19 @@ struct reg_feat_map_desc {
#define FEAT_GCIE ID_AA64PFR2_EL1, GCIE, IMP
#define FEAT_NV3 ID_AA64MMFR4_EL1, NV_frac, NV3
+static bool feat_asid16(struct kvm *kvm)
+{
+ return kvm_has_feat_enum(kvm, ID_AA64MMFR0_EL1, ASIDBITS, 16);
+}
+
+static bool feat_mte_no_addr_tags_or_canonical_tags(struct kvm *kvm)
+{
+ bool no_addr_tags = kvm_has_feat(kvm, FEAT_MTE_NO_ADDRESS_TAGS);
+ bool canonical_tags = kvm_has_feat(kvm, FEAT_MTE_CANONICAL_TAGS);
+
+ return no_addr_tags || canonical_tags;
+}
+
static bool not_feat_aa64el3(struct kvm *kvm)
{
return !kvm_has_feat(kvm, FEAT_AA64EL3);
@@ -1044,6 +1064,91 @@ static const struct reg_bits_to_feat_map sctlr2_feat_map[] = {
static const DECLARE_FEAT_MAP(sctlr2_desc, SCTLR2_EL1,
sctlr2_feat_map, FEAT_SCTLR2);
+static const struct reg_bits_to_feat_map tcr_el2_vhe_feat_map[] = {
+ NEEDS_FEAT(VHE_TCR_EL2_MTX1 |
+ VHE_TCR_EL2_MTX0,
+ feat_mte_no_addr_tags_or_canonical_tags),
+ NEEDS_FEAT(VHE_TCR_EL2_DS, feat_lpa2),
+ NEEDS_FEAT(VHE_TCR_EL2_TCMA1 |
+ VHE_TCR_EL2_TCMA0,
+ FEAT_MTE2),
+ NEEDS_FEAT(VHE_TCR_EL2_E0PD1 |
+ VHE_TCR_EL2_E0PD0,
+ FEAT_E0PD),
+ NEEDS_FEAT(VHE_TCR_EL2_NFD1 |
+ VHE_TCR_EL2_NFD0,
+ FEAT_SVE),
+ NEEDS_FEAT(VHE_TCR_EL2_TBID1 |
+ VHE_TCR_EL2_TBID0,
+ feat_pauth),
+ NEEDS_FEAT(VHE_TCR_EL2_HWU162 |
+ VHE_TCR_EL2_HWU161 |
+ VHE_TCR_EL2_HWU160 |
+ VHE_TCR_EL2_HWU159 |
+ VHE_TCR_EL2_HWU062 |
+ VHE_TCR_EL2_HWU061 |
+ VHE_TCR_EL2_HWU060 |
+ VHE_TCR_EL2_HWU059,
+ FEAT_HPDS2),
+ NEEDS_FEAT(VHE_TCR_EL2_HPD1 |
+ VHE_TCR_EL2_HPD0,
+ FEAT_HPDS),
+ NEEDS_FEAT(VHE_TCR_EL2_HD, FEAT_HAFDBS),
+ NEEDS_FEAT(VHE_TCR_EL2_HA, FEAT_HAF),
+ NEEDS_FEAT(VHE_TCR_EL2_AS, feat_asid16),
+ NEEDS_FEAT(VHE_TCR_EL2_TBI1 |
+ VHE_TCR_EL2_TBI0 |
+ VHE_TCR_EL2_IPS |
+ VHE_TCR_EL2_TG1 |
+ VHE_TCR_EL2_SH1 |
+ VHE_TCR_EL2_ORGN1 |
+ VHE_TCR_EL2_IRGN1 |
+ VHE_TCR_EL2_EPD1 |
+ VHE_TCR_EL2_A1 |
+ VHE_TCR_EL2_T1SZ |
+ VHE_TCR_EL2_TG0 |
+ VHE_TCR_EL2_SH0 |
+ VHE_TCR_EL2_ORGN0 |
+ VHE_TCR_EL2_IRGN0 |
+ VHE_TCR_EL2_EPD0 |
+ VHE_TCR_EL2_T0SZ,
+ FEAT_AA64EL2),
+ FORCE_RES0(VHE_TCR_EL2_RES0),
+ FORCE_RES1(VHE_TCR_EL2_RES1),
+};
+
+static const DECLARE_FEAT_MAP(tcr_el2_vhe_desc, VHE_TCR_EL2,
+ tcr_el2_vhe_feat_map, FEAT_AA64EL2);
+
+static const struct reg_bits_to_feat_map tcr_el2_nvhe_feat_map[] = {
+ NEEDS_FEAT(TCR_EL2_MTX,
+ feat_mte_no_addr_tags_or_canonical_tags),
+ NEEDS_FEAT(TCR_EL2_DS, feat_lpa2),
+ NEEDS_FEAT(TCR_EL2_TCMA, FEAT_MTE2),
+ NEEDS_FEAT(TCR_EL2_TBID, feat_pauth),
+ NEEDS_FEAT(TCR_EL2_HWU62 |
+ TCR_EL2_HWU61 |
+ TCR_EL2_HWU60 |
+ TCR_EL2_HWU59,
+ FEAT_HPDS2),
+ NEEDS_FEAT(TCR_EL2_HPD, FEAT_HPDS),
+ NEEDS_FEAT(TCR_EL2_HD, FEAT_HAFDBS),
+ NEEDS_FEAT(TCR_EL2_HA, FEAT_HAF),
+ NEEDS_FEAT(TCR_EL2_TBI |
+ TCR_EL2_PS |
+ TCR_EL2_TG0 |
+ TCR_EL2_SH0 |
+ TCR_EL2_ORGN0 |
+ TCR_EL2_IRGN0 |
+ TCR_EL2_T0SZ,
+ FEAT_AA64EL2),
+ FORCE_RES0(TCR_EL2_RES0),
+ FORCE_RES1(TCR_EL2_RES1),
+};
+
+static const DECLARE_FEAT_MAP(tcr_el2_nvhe_desc, TCR_EL2,
+ tcr_el2_nvhe_feat_map, FEAT_AA64EL2);
+
static const struct reg_bits_to_feat_map tcr2_el2_feat_map[] = {
NEEDS_FEAT_FLAG(TCR2_EL2_FNG1 |
TCR2_EL2_FNG0 |
@@ -1398,6 +1503,8 @@ void __init check_feature_map(void)
check_reg_desc(&hcr_desc);
check_reg_desc(&nvhcr_desc);
check_reg_desc(&sctlr2_desc);
+ check_reg_desc(&tcr_el2_vhe_desc);
+ check_reg_desc(&tcr_el2_nvhe_desc);
check_reg_desc(&tcr2_el2_desc);
check_reg_desc(&sctlr_el1_desc);
check_reg_desc(&sctlr_el2_desc);
@@ -1611,6 +1718,12 @@ struct resx get_reg_fixed_bits(struct kvm *kvm, enum vcpu_sysreg reg)
case SCTLR2_EL2:
resx = compute_reg_resx_bits(kvm, &sctlr2_desc, 0, 0);
break;
+ case TCR_EL2:
+ if (kvm_has_feat(kvm, FEAT_E2H0))
+ resx = compute_reg_resx_bits(kvm, &tcr_el2_nvhe_desc, 0, 0);
+ else
+ resx = compute_reg_resx_bits(kvm, &tcr_el2_vhe_desc, 0, 0);
+ break;
case TCR2_EL2:
resx = compute_reg_resx_bits(kvm, &tcr2_el2_desc, 0, 0);
break;
diff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c
index 1fd98117df23..5aa16d9d555b 100644
--- a/arch/arm64/kvm/nested.c
+++ b/arch/arm64/kvm/nested.c
@@ -1959,6 +1959,10 @@ int kvm_init_nv_sysregs(struct kvm_vcpu *vcpu)
resx = get_reg_fixed_bits(kvm, HFGITR2_EL2);
set_sysreg_masks(kvm, HFGITR2_EL2, resx);
+ /* TCR_EL2 */
+ resx = get_reg_fixed_bits(kvm, TCR_EL2);
+ set_sysreg_masks(kvm, TCR_EL2, resx);
+
/* TCR2_EL2 */
resx = get_reg_fixed_bits(kvm, TCR2_EL2);
set_sysreg_masks(kvm, TCR2_EL2, resx);
--
2.43.0