Re: [PATCH v1 3/3] KVM: arm64: Convert TCR_EL2 to config-driven sanitisation
From: Marc Zyngier
Date: Fri Sep 11 2026 - 05:07:22 EST
On Wed, 09 Sep 2026 23:20:15 +0100,
Wei-Lin Chang <weilin.chang@xxxxxxx> wrote:
>
> 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.
The runtime stuff is never something we can do as part of the static
initialisation. I think the way this is captured in the JSON is a bit
hackish, and it should probably say:
IsFeatureImplemented(FEAT_LPA2) && (EffectiveValue(TCR2_EL2.D128) == '0')
which would naturally work.
>
> - 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[] = {
Maybe be consistent with the generated field names and call the array
'vhe_tcr_el2_feat_map'. Similarly for all the other names.
> + 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[] = {
and drop the nvhe here.
Thanks,
M.
--
Without deviation from the norm, progress is not possible.