[RFC 5/6] KVM: arm64: Conditionally expose ID_AA64MMFR4_EL1.TLBID to guest

Jinqian Yang yangjinqian1 at huawei.com
Thu Oct 29 23:56:16 PDT 2026


The TLBID feature bit in ID_AA64MMFR4_EL1 is only advertised to the
guest after userspace configures vDomains via KVM_ARM_VCPU_SET_VDOMAIN,
which sets the guest_tlbid_enabled flag. Until then the bit is masked
to prevent the guest from enabling TLBID without a valid vDomain
affinity.

Exposing the feature would otherwise let the guest read the physical
TLBIDIDR_EL1 directly, so keep that register trapped in the guest
FGT context and served by the emulation added earlier. The host's
own register is never affected.

Expose KVM_REG_ARM_ID_AA64MMFR4_EL1 in the arm64 UAPI header so
userspace can read the sanitised value via KVM_GET_ONE_REG.

Signed-off-by: Jinqian Yang <yangjinqian1 at huawei.com>
Signed-off-by: Tian Zheng <zhengtian10 at huawei.com>
---
 arch/arm64/include/uapi/asm/kvm.h |  2 ++
 arch/arm64/kvm/arm.c              |  2 ++
 arch/arm64/kvm/config.c           | 11 +++++++
 arch/arm64/kvm/sys_regs.c         | 49 +++++++++++++++++++++++++++++--
 arch/arm64/kvm/sys_regs.h         |  6 ++++
 5 files changed, 68 insertions(+), 2 deletions(-)

diff --git a/arch/arm64/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h
index 019e5e3d892e..e54129ed431d 100644
--- a/arch/arm64/include/uapi/asm/kvm.h
+++ b/arch/arm64/include/uapi/asm/kvm.h
@@ -255,6 +255,8 @@ struct kvm_arm_counter_offset {
 
 #define ARM64_SYS_REG(...) (__ARM64_SYS_REG(__VA_ARGS__) | KVM_REG_SIZE_U64)
 
+#define KVM_REG_ARM_ID_AA64MMFR4_EL1 ARM64_SYS_REG(3, 0, 0, 7, 4)
+
 /* Physical Timer EL0 Registers */
 #define KVM_REG_ARM_PTIMER_CTL		ARM64_SYS_REG(3, 3, 14, 2, 1)
 #define KVM_REG_ARM_PTIMER_CVAL		ARM64_SYS_REG(3, 3, 14, 2, 2)
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index ddf7c25f9ed9..b5ab89db1c07 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -2313,6 +2313,8 @@ static int kvm_arm_vcpu_set_vdomain(struct kvm_vcpu *vcpu,
 
 	kvm_info("vCPU%d: vDomain bitmap: 0x%x\n", vcpu->vcpu_idx, vdomain_bitmap);
 
+	kvm_update_aa64mmfr4_tlbid(vcpu->kvm);
+
 	return 0;
 }
 #else
diff --git a/arch/arm64/kvm/config.c b/arch/arm64/kvm/config.c
index 6c116aa1864a..d0402151a752 100644
--- a/arch/arm64/kvm/config.c
+++ b/arch/arm64/kvm/config.c
@@ -1536,6 +1536,17 @@ void compute_fgu(struct kvm *kvm, enum fgt_group_id fgt)
 	case HFGRTR2_GROUP:
 		val |= compute_fgu_bits(kvm, &hfgrtr2_desc);
 		val |= compute_fgu_bits(kvm, &hfgwtr2_desc);
+#ifdef CONFIG_KVM_ARM_VTLBID
+		/*
+		 * TLBIDIDR_EL1 must stay trapped even when TLBID is
+		 * exposed to the guest, so it can be emulated with the
+		 * synthesized virtual domain counts. This only affects
+		 * the guest FGT context; the host's own HFGRTR2_EL2 is
+		 * never modified.
+		 */
+		if (kvm->arch.tlbidomain.kvm_tlbid_enabled)
+			val |= HFGRTR2_EL2_nTLBIDIDR_EL1;
+#endif
 		break;
 	case HFGITR2_GROUP:
 		val |= compute_fgu_bits(kvm, &hfgitr2_desc);
diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index cf9804e54ff1..a1ca7309406e 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -2012,9 +2012,17 @@ static u64 __kvm_read_sanitised_id_reg(const struct kvm_vcpu *vcpu,
 		if (!system_supports_poe())
 			val &= ~ID_AA64MMFR3_EL1_S1POE;
 		break;
-	case SYS_ID_AA64MMFR4_EL1:
+	case SYS_ID_AA64MMFR4_EL1: {
+#ifdef CONFIG_KVM_ARM_VTLBID
+		struct tlbidomain *vdomain = &vcpu->kvm->arch.tlbidomain;
+
+		if (!vdomain->guest_tlbid_enabled)
+			val &= ~ID_AA64MMFR4_EL1_TLBID;
+#else
 		val &= ~ID_AA64MMFR4_EL1_TLBID;
+#endif
 		break;
+	}
 	case SYS_ID_MMFR4_EL1:
 		val &= ~ID_MMFR4_EL1_CCIDX;
 		break;
@@ -3494,7 +3502,8 @@ static const struct sys_reg_desc sys_reg_descs[] = {
 				       ID_AA64MMFR3_EL1_SCTLRX	|
 				       ID_AA64MMFR3_EL1_S1PIE   |
 				       ID_AA64MMFR3_EL1_S1POE)),
-	ID_WRITABLE(ID_AA64MMFR4_EL1, ID_AA64MMFR4_EL1_NV_frac),
+	ID_WRITABLE(ID_AA64MMFR4_EL1, ID_AA64MMFR4_EL1_NV_frac |
+				    ID_AA64MMFR4_EL1_TLBID_MASK),
 	ID_UNALLOCATED(7,5),
 	ID_UNALLOCATED(7,6),
 	ID_UNALLOCATED(7,7),
@@ -5495,6 +5504,42 @@ static bool index_to_params(u64 id, struct sys_reg_params *params)
 	}
 }
 
+/*
+ * Update the VM's stored value for an ID register.
+ */
+#ifdef CONFIG_KVM_ARM_VTLBID
+void kvm_update_aa64mmfr4_tlbid(struct kvm *kvm)
+{
+	struct tlbidomain *vdomain = &kvm->arch.tlbidomain;
+	u64 val, tlbid_val;
+
+	mutex_lock(&kvm->arch.config_lock);
+
+	if (kvm_vm_has_ran_once(kvm)) {
+		/*
+		 * ID_AA64MMFR4 is a VM-level register already set during
+		 * the first vCPU init. Skip the write to avoid triggering
+		 * KVM_BUG_ON in kvm_set_vm_id_reg(). The per-vCPU
+		 * vdomain_bitmap is still updated by the caller.
+		 */
+		mutex_unlock(&kvm->arch.config_lock);
+		return;
+	}
+
+	val = kvm_read_vm_id_reg(kvm, SYS_ID_AA64MMFR4_EL1);
+	tlbid_val = read_sanitised_ftr_reg(SYS_ID_AA64MMFR4_EL1);
+	tlbid_val &= ID_AA64MMFR4_EL1_TLBID_MASK;
+
+	val &= ~ID_AA64MMFR4_EL1_TLBID_MASK;
+	if (vdomain->guest_tlbid_enabled)
+		val |= tlbid_val;
+
+	kvm_set_vm_id_reg(kvm, SYS_ID_AA64MMFR4_EL1, val);
+
+	mutex_unlock(&kvm->arch.config_lock);
+}
+#endif
+
 const struct sys_reg_desc *get_reg_by_id(u64 id,
 					 const struct sys_reg_desc table[],
 					 unsigned int num)
diff --git a/arch/arm64/kvm/sys_regs.h b/arch/arm64/kvm/sys_regs.h
index bd56a45abbf9..0b5487947790 100644
--- a/arch/arm64/kvm/sys_regs.h
+++ b/arch/arm64/kvm/sys_regs.h
@@ -252,6 +252,12 @@ int kvm_sys_reg_get_user(struct kvm_vcpu *vcpu, const struct kvm_one_reg *reg,
 int kvm_sys_reg_set_user(struct kvm_vcpu *vcpu, const struct kvm_one_reg *reg,
 			 const struct sys_reg_desc table[], unsigned int num);
 
+#ifdef CONFIG_KVM_ARM_VTLBID
+void kvm_update_aa64mmfr4_tlbid(struct kvm *kvm);
+#else
+static inline void kvm_update_aa64mmfr4_tlbid(struct kvm *kvm) { }
+#endif
+
 bool triage_sysreg_trap(struct kvm_vcpu *vcpu, int *sr_index);
 
 int kvm_finalize_sys_regs(struct kvm_vcpu *vcpu);
-- 
2.33.0




More information about the linux-arm-kernel mailing list