Skip to contentSkip to conversation

KVM: arm64: Trap guest MPAM accesses whenever MPAM is implemented

PATCH Superseded Fuad Tabba 4 participants 10 messages Sep 3 – Sep 8

Subsystems (2)

v1 of 5 Superseded Latest is v5 →

lore
Share & tools
lore

Fuad Tabba

author reviewer
#0 lore raw

PATCH 1/1 KVM: arm64: Trap guest MPAM accesses whenever MPAM is implemented

finalise_el2_state() clears the EL2 MPAM traps whenever the ID registers advertise MPAM, but KVM sets them only when ARM64_MPAM is set, which also requires MPAMEN. Without EL3 the enable is EL2's own and nothing sets it, so the cap stays off and a guest reaches the MPAM registers while ID_AA64PFR0_EL1.MPAM reads 0 for it.

Gate the traps on the ID registers alone. MPAMEN is not a term in any MPAM accessor, so they take effect without it. finalise_el2_state already wrote MPAM2_EL2 under the same condition, so MPAM3_EL3.TRAPLOWER is clear wherever the cap is set, and arm64.nompam still clears it.

Fixes 31ff96c38ea3 ("KVM: arm64: Fix missing traps of guest accesses to the MPAM registers")

---
Found this while working on the other MPAM thread [1].

[1] https://lore.kernel.org/all/CA+EHjTxeWxZiuSmnKLGLxTBXP4oJT7-LuffbPAyCSZZ5TW=5Ew@mail.gmail.com/

 arch/arm64/include/asm/cpufeature.h     |  5 +++++
 arch/arm64/kernel/cpufeature.c          | 13 +++++++++++++
 arch/arm64/kvm/hyp/include/hyp/switch.h |  4 ++--
 arch/arm64/tools/cpucaps                |  1 +
 4 files changed, 21 insertions(+), 2 deletions(-)
arch/arm64/include/asm/cpufeature.h +5 -0
index 7404a6e83a930..8863ae99596bc 100644
@@ -873,6 +873,11 @@ static __always_inline bool system_supports_mpam_hcr(void)
 	return alternative_has_cap_unlikely(ARM64_MPAM_HCR);
 }
 
+static __always_inline bool system_supports_mpam_sysregs(void)
+{
+	return alternative_has_cap_unlikely(ARM64_MPAM_SYSREGS);
+}
+
 static inline bool system_supports_pmuv3(void)
 {
 	return cpus_have_final_cap(ARM64_HAS_PMUV3);
arch/arm64/kernel/cpufeature.c +13 -0
index 17b83a2518a8f..36a27692e5cf7 100644
@@ -2501,6 +2501,13 @@ test_has_mpam(const struct arm64_cpu_capabilities *entry, int scope)
 	return (read_sysreg_s(SYS_MPAM1_EL1) & MPAM1_EL1_MPAMEN);
 }
 
+static bool
+test_has_mpam_sysregs(const struct arm64_cpu_capabilities *entry, int __unused)
+{
+	/* The registers exist whether or not firmware enabled MPAM. */
+	return detect_ftr_has_mpam();
+}
+
 static void
 cpu_enable_mpam(const struct arm64_cpu_capabilities *entry)
 {
@@ -3116,6 +3123,12 @@ static const struct arm64_cpu_capabilities arm64_features[] = {
 		.matches = test_has_mpam,
 		.cpu_enable = cpu_enable_mpam,
 	},
+	{
+		.desc = "Memory Partitioning And Monitoring system registers",
+		.type = ARM64_CPUCAP_SYSTEM_FEATURE,
+		.capability = ARM64_MPAM_SYSREGS,
+		.matches = test_has_mpam_sysregs,
+	},
 	{
 		.desc = "Memory Partitioning And Monitoring Virtualisation",
 		.type = ARM64_CPUCAP_SYSTEM_FEATURE,
arch/arm64/kvm/hyp/include/hyp/switch.h +2 -2
index 1ce7130e25490..8941335724f6b 100644
@@ -298,7 +298,7 @@ static inline void  __activate_traps_mpam(struct kvm_vcpu *vcpu)
 	u64 clr = MPAM2_EL2_EnMPAMSM;
 	u64 set = MPAM2_EL2_TRAPMPAM0EL1 | MPAM2_EL2_TRAPMPAM1EL1;
 
-	if (!system_supports_mpam())
+	if (!system_supports_mpam_sysregs())
 		return;
 
 	/* trap guest access to MPAMIDR_EL1 */
@@ -317,7 +317,7 @@ static inline void __deactivate_traps_mpam(void)
 	u64 clr = MPAM2_EL2_TRAPMPAM0EL1 | MPAM2_EL2_TRAPMPAM1EL1 | MPAM2_EL2_TIDR;
 	u64 set = MPAM2_EL2_EnMPAMSM;
 
-	if (!system_supports_mpam())
+	if (!system_supports_mpam_sysregs())
 		return;
 
 	sysreg_clear_set_s(SYS_MPAM2_EL2, clr, set);
arch/arm64/tools/cpucaps +1 -0
index 2775ba3359cfe..aa5be51385f68 100644
@@ -78,6 +78,7 @@ KVM_PROTECTED_MODE
 MISMATCHED_CACHE_TYPE
 MPAM
 MPAM_HCR
+MPAM_SYSREGS
 MTE
 MTE_ASYMM
 MTE_FAR

base-commit: cee9395acd8043be0644b25c34bfa86623f2b935

Signature · 1 line
2.39.5
#1 lore raw
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] KVM fails to trap guest accesses to MPAM registers when the sanitized MPAM capability evaluates to false (e.g., due to arm64.nompam boot parameter or asymmetric CPU topologies), allowing the guest to directly access physical host MPAM hardware. --

commit 4af5e7631ab3a7072311a3251a8c2768e198ef2e
Author: Fuad Tabba <[email protected]>

KVM: arm64: Trap guest MPAM accesses whenever MPAM is implemented

This patch updates KVM to trap guest accesses to MPAM registers based solely on the ID registers rather than the ARM64_MPAM capability, which also requires MPAMEN to be set.

Quoted 5 lines
arch/arm64/kernel/cpufeature.c +0 -0
index 17b83a2518a8f..36a27692e5cf7 100644

arch/arm64/kernel/cpufeature.c test_has_mpam(const struct arm64_cpu_capabilities *entry, int scope) · line 2510

Quoted 10 lines
arch/arm64/kernel/cpufeature.c +7 -0
@@ -2501,6 +2501,13 @@ test_has_mpam(const struct arm64_cpu_capabilities *entry, int scope)
 	return (read_sysreg_s(SYS_MPAM1_EL1) & MPAM1_EL1_MPAMEN);
 }
 
+static bool
+test_has_mpam_sysregs(const struct arm64_cpu_capabilities *entry, int __unused)
+{
+	/* The registers exist whether or not firmware enabled MPAM. */
+	return detect_ftr_has_mpam();
+}
+

[Severity: High]
This isn't a bug introduced by this patch, but it appears to be a pre-existing issue. Does relying on detect_ftr_has_mpam() here leave the physical MPAM hardware exposed?

The detect_ftr_has_mpam() function checks the sanitised CPU feature registers. If the host is booted with the arm64.nompam parameter, or on systems with asymmetric CPU topologies where the boot CPU lacks MPAM, this check will evaluate to false even if the physical hardware on the current CPU implements MPAM.

[ ... ]

arch/arm64/kvm/hyp/include/hyp/switch.h static inline void __activate_traps_mpam(struct kvm_vcpu *vcpu) · line 304

Quoted 13 lines
arch/arm64/kvm/hyp/include/hyp/switch.h +1 -1
index 1ce7130e25490..8941335724f6b 100644
@@ -298,7 +298,7 @@ static inline void  __activate_traps_mpam(struct kvm_vcpu *vcpu)
 	u64 clr = MPAM2_EL2_EnMPAMSM;
 	u64 set = MPAM2_EL2_TRAPMPAM0EL1 | MPAM2_EL2_TRAPMPAM1EL1;
 
-	if (!system_supports_mpam())
+	if (!system_supports_mpam_sysregs())
 		return;
 
 	/* trap guest access to MPAMIDR_EL1 */

[Severity: High]
This is also part of the pre-existing issue mentioned above.

When system_supports_mpam_sysregs() returns false due to the sanitised feature registers, this early return skips setting the TRAPMPAM0EL1 and TRAPMPAM1EL1 trap bits in SYS_MPAM2_EL2.

Since the hardware traps remain disabled, could this allow a malicious guest to directly access physical MPAM configuration registers (such as MPAM0_EL1) and bypass hypervisor isolation? This might allow the guest to manipulate host cache and memory bandwidth partitioning, potentially causing a denial of service against the host or other VMs.

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
author reviewer
#2 lore raw
Quoted 5 lines · On Thu, 3 Sept 2026 at 17:25, <[email protected]> wrote:

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] KVM fails to trap guest accesses to MPAM registers when the sanitized MPAM capability evaluates to false (e.g., due to arm64.nompam boot parameter or asymmetric CPU topologies), allowing the guest to directly access physical host MPAM hardware.

See : https://lore.kernel.org/all/[email protected]/

/fuad

Quoted 71 lines

--

commit 4af5e7631ab3a7072311a3251a8c2768e198ef2e
Author: Fuad Tabba <[email protected]>

KVM: arm64: Trap guest MPAM accesses whenever MPAM is implemented

This patch updates KVM to trap guest accesses to MPAM registers based solely on the ID registers rather than the ARM64_MPAM capability, which also requires MPAMEN to be set.

Quoted 5 lines
arch/arm64/kernel/cpufeature.c +0 -0
index 17b83a2518a8f..36a27692e5cf7 100644

arch/arm64/kernel/cpufeature.c test_has_mpam(const struct arm64_cpu_capabilities *entry, int scope) · line 2510

Quoted 10 lines
arch/arm64/kernel/cpufeature.c +7 -0
@@ -2501,6 +2501,13 @@ test_has_mpam(const struct arm64_cpu_capabilities *entry, int scope)
      return (read_sysreg_s(SYS_MPAM1_EL1) & MPAM1_EL1_MPAMEN);
 }
 
+static bool
+test_has_mpam_sysregs(const struct arm64_cpu_capabilities *entry, int __unused)
+{
+     /* The registers exist whether or not firmware enabled MPAM. */
+     return detect_ftr_has_mpam();
+}
+

[Severity: High]
This isn't a bug introduced by this patch, but it appears to be a pre-existing issue. Does relying on detect_ftr_has_mpam() here leave the physical MPAM hardware exposed?

The detect_ftr_has_mpam() function checks the sanitised CPU feature registers. If the host is booted with the arm64.nompam parameter, or on systems with asymmetric CPU topologies where the boot CPU lacks MPAM, this check will evaluate to false even if the physical hardware on the current CPU implements MPAM.

[ ... ]

arch/arm64/kvm/hyp/include/hyp/switch.h static inline void __activate_traps_mpam(struct kvm_vcpu *vcpu) · line 304

Quoted 13 lines
arch/arm64/kvm/hyp/include/hyp/switch.h +1 -1
index 1ce7130e25490..8941335724f6b 100644
@@ -298,7 +298,7 @@ static inline void  __activate_traps_mpam(struct kvm_vcpu *vcpu)
      u64 clr = MPAM2_EL2_EnMPAMSM;
      u64 set = MPAM2_EL2_TRAPMPAM0EL1 | MPAM2_EL2_TRAPMPAM1EL1;
 
-     if (!system_supports_mpam())
+     if (!system_supports_mpam_sysregs())
              return;
 
      /* trap guest access to MPAMIDR_EL1 */

[Severity: High]
This is also part of the pre-existing issue mentioned above.

When system_supports_mpam_sysregs() returns false due to the sanitised feature registers, this early return skips setting the TRAPMPAM0EL1 and TRAPMPAM1EL1 trap bits in SYS_MPAM2_EL2.

Since the hardware traps remain disabled, could this allow a malicious guest to directly access physical MPAM configuration registers (such as MPAM0_EL1) and bypass hypervisor isolation? This might allow the guest to manipulate host cache and memory bandwidth partitioning, potentially causing a denial of service against the host or other VMs.

--
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1

#3 lore raw
In reply to Fuad Tabba
Quoted 45 lines · Fuad Tabba

finalise_el2_state() clears the EL2 MPAM traps whenever the ID registers advertise MPAM, but KVM sets them only when ARM64_MPAM is set, which also requires MPAMEN. Without EL3 the enable is EL2's own and nothing sets it, so the cap stays off and a guest reaches the MPAM registers while ID_AA64PFR0_EL1.MPAM reads 0 for it.

Gate the traps on the ID registers alone. MPAMEN is not a term in any MPAM accessor, so they take effect without it. finalise_el2_state already wrote MPAM2_EL2 under the same condition, so MPAM3_EL3.TRAPLOWER is clear wherever the cap is set, and arm64.nompam still clears it.

Fixes 31ff96c38ea3 ("KVM: arm64: Fix missing traps of guest accesses to the MPAM registers")

---
Found this while working on the other MPAM thread [1].

[1] https://lore.kernel.org/all/CA+EHjTxeWxZiuSmnKLGLxTBXP4oJT7-LuffbPAyCSZZ5TW=5Ew@mail.gmail.com/

 arch/arm64/include/asm/cpufeature.h     |  5 +++++
 arch/arm64/kernel/cpufeature.c          | 13 +++++++++++++
 arch/arm64/kvm/hyp/include/hyp/switch.h |  4 ++--
 arch/arm64/tools/cpucaps                |  1 +
 4 files changed, 21 insertions(+), 2 deletions(-)
arch/arm64/include/asm/cpufeature.h +5 -0
index 7404a6e83a930..8863ae99596bc 100644
@@ -873,6 +873,11 @@ static __always_inline bool system_supports_mpam_hcr(void)
 	return alternative_has_cap_unlikely(ARM64_MPAM_HCR);
 }
 
+static __always_inline bool system_supports_mpam_sysregs(void)
+{
+	return alternative_has_cap_unlikely(ARM64_MPAM_SYSREGS);
+}
+
 static inline bool system_supports_pmuv3(void)
 {
 	return cpus_have_final_cap(ARM64_HAS_PMUV3);
arch/arm64/kernel/cpufeature.c +0 -0
index 17b83a2518a8f..36a27692e5cf7 100644

arch/arm64/kernel/cpufeature.c test_has_mpam(const struct arm64_cpu_capabilities *entry, int scope) · line 2503

Quoted 3 lines
arch/arm64/kernel/cpufeature.c +0 -0
@@ -2501,6 +2501,13 @@ test_has_mpam(const struct arm64_cpu_capabilities *entry, int scope)
 	return (read_sysreg_s(SYS_MPAM1_EL1) & MPAM1_EL1_MPAMEN);
 }
 

Hi Tabba,

arch/arm64/kernel/cpufeature.c test_has_mpam(const struct arm64_cpu_capabilities *entry, int scope) · line 2509

Quoted 6 lines
arch/arm64/kernel/cpufeature.c +6 -0
+static bool
+test_has_mpam_sysregs(const struct arm64_cpu_capabilities *entry, int __unused)
+{
+	/* The registers exist whether or not firmware enabled MPAM. */
+	return detect_ftr_has_mpam();
+}

My understanding: arm64.nompam affects detect_ftr_has_mpam(), thus when arm64.nompam = 1 w/ MPAM is supported in hardware, the KVM's trap setting is skipped yet, and it's possible that SYS_MPAM2_EL2 and SYS_MPAMHCR_EL2 are configured not trap anything by firmware, thus guest can still access MPAM registers.

Do we need check the raw id register values for the real support state of MPAM here ?

Quoted 56 lines
arch/arm64/kernel/cpufeature.c +7 -0
+
 static void
 cpu_enable_mpam(const struct arm64_cpu_capabilities *entry)
 {
@@ -3116,6 +3123,12 @@ static const struct arm64_cpu_capabilities arm64_features[] = {
 		.matches = test_has_mpam,
 		.cpu_enable = cpu_enable_mpam,
 	},
+	{
+		.desc = "Memory Partitioning And Monitoring system registers",
+		.type = ARM64_CPUCAP_SYSTEM_FEATURE,
+		.capability = ARM64_MPAM_SYSREGS,
+		.matches = test_has_mpam_sysregs,
+	},
 	{
 		.desc = "Memory Partitioning And Monitoring Virtualisation",
 		.type = ARM64_CPUCAP_SYSTEM_FEATURE,
arch/arm64/kvm/hyp/include/hyp/switch.h +2 -2
index 1ce7130e25490..8941335724f6b 100644
@@ -298,7 +298,7 @@ static inline void  __activate_traps_mpam(struct kvm_vcpu *vcpu)
 	u64 clr = MPAM2_EL2_EnMPAMSM;
 	u64 set = MPAM2_EL2_TRAPMPAM0EL1 | MPAM2_EL2_TRAPMPAM1EL1;
 
-	if (!system_supports_mpam())
+	if (!system_supports_mpam_sysregs())
 		return;
 
 	/* trap guest access to MPAMIDR_EL1 */
@@ -317,7 +317,7 @@ static inline void __deactivate_traps_mpam(void)
 	u64 clr = MPAM2_EL2_TRAPMPAM0EL1 | MPAM2_EL2_TRAPMPAM1EL1 | MPAM2_EL2_TIDR;
 	u64 set = MPAM2_EL2_EnMPAMSM;
 
-	if (!system_supports_mpam())
+	if (!system_supports_mpam_sysregs())
 		return;
 
 	sysreg_clear_set_s(SYS_MPAM2_EL2, clr, set);
arch/arm64/tools/cpucaps +1 -0
index 2775ba3359cfe..aa5be51385f68 100644
@@ -78,6 +78,7 @@ KVM_PROTECTED_MODE
 MISMATCHED_CACHE_TYPE
 MPAM
 MPAM_HCR
+MPAM_SYSREGS
 MTE
 MTE_ASYMM
 MTE_FAR

base-commit: cee9395acd8043be0644b25c34bfa86623f2b935 --
2.39.5

author reviewer
#4 lore raw

Hi Yuan,

Quoted 13 lines

arch/arm64/kernel/cpufeature.c test_has_mpam(const struct arm64_cpu_capabilities *entry, int scope) · line 2509

Quoted 6 lines
arch/arm64/kernel/cpufeature.c +6 -0
+static bool
+test_has_mpam_sysregs(const struct arm64_cpu_capabilities *entry, int __unused)
+{
+ /* The registers exist whether or not firmware enabled MPAM. */
+ return detect_ftr_has_mpam();
+}

My understanding: arm64.nompam affects detect_ftr_has_mpam(), thus when arm64.nompam = 1 w/ MPAM is supported in hardware, the KVM's trap setting is skipped yet, and it's possible that SYS_MPAM2_EL2 and SYS_MPAMHCR_EL2 are configured not trap anything by firmware, thus guest can still access MPAM registers.

Quoted 2 lines

Do we need check the raw id register values for the real support state of MPAM here ?

Under arm64.nompam, finalise_el2_state skips the MPAM2_EL2 and MPAMHCR_EL2 writes too, via the same check_override, so the kernel does not clear the traps either. What a guest reaches there is whatever EL3 left, UNKNOWN when EL3 is implemented. This patch is for the case where the kernel cleared the traps itself and never set them again.

Gating on the raw ID registers would make __activate_traps_mpam() write MPAM2_EL2 on guest entry there. AFAICT that traps to EL3 wherever MPAM3_EL3.TRAPLOWER is still set, which is the firmware the option is for: 10f885d63a0e ("arm64: Add override for MPAM") added it for firmware that leaves the trap set and does not emulate it.

The arm64.nompam hazard is documented separately [1].

Cheers,
/fuad

[1] https://lore.kernel.org/all/[email protected]/

Reviewed-by
#5 lore raw
Quoted 15 lines · Fuad Tabba

Hi Yuan,

Quoted 13 lines

arch/arm64/kernel/cpufeature.c test_has_mpam(const struct arm64_cpu_capabilities *entry, int scope) · line 2509

Quoted 6 lines
arch/arm64/kernel/cpufeature.c +6 -0
+static bool
+test_has_mpam_sysregs(const struct arm64_cpu_capabilities *entry, int __unused)
+{
+ /* The registers exist whether or not firmware enabled MPAM. */
+ return detect_ftr_has_mpam();
+}

My understanding: arm64.nompam affects detect_ftr_has_mpam(), thus when arm64.nompam = 1 w/ MPAM is supported in hardware, the KVM's trap setting is skipped yet, and it's possible that SYS_MPAM2_EL2 and SYS_MPAMHCR_EL2 are configured not trap anything by firmware, thus guest can still access MPAM registers.

Quoted 7 lines
Quoted 2 lines

Do we need check the raw id register values for the real support state of MPAM here ?

Under arm64.nompam, finalise_el2_state skips the MPAM2_EL2 and MPAMHCR_EL2 writes too, via the same check_override, so the kernel does not clear the traps either. What a guest reaches there is whatever EL3 left, UNKNOWN when EL3 is implemented.

That's what I was worried before w/ nompam = 1 for guest can still access the MPAM registers. Now it's fine to me w/ limitation on usage of nompam described in [1], the limitation on guest behavior described there yet. Thanks for the reply! I just not aware [1] before.

Quoted 2 lines

This patch is for the case where the kernel cleared the traps itself and never set them again.

Yes next time I will explicitly say want to discuss something may related to the patch's main purpose.

Quoted 6 lines

Gating on the raw ID registers would make __activate_traps_mpam() write MPAM2_EL2 on guest entry there. AFAICT that traps to EL3 wherever MPAM3_EL3.TRAPLOWER is still set, which is the firmware the option is for: 10f885d63a0e ("arm64: Add override for MPAM") added it for firmware that leaves the trap set and does not emulate it.

Make sense, it break the nompam actually on platform need it, this isn't good idea.

For the purpose of this patch:

Quoted 7 lines

The arm64.nompam hazard is documented separately [1].

Cheers,
/fuad

[1] https://lore.kernel.org/all/[email protected]/

#6 lore raw
In reply to Fuad Tabba

Hi Fuad,

Quoted 5 lines · Fuad Tabba

finalise_el2_state() clears the EL2 MPAM traps whenever the ID registers advertise MPAM, but KVM sets them only when ARM64_MPAM is set, which also requires MPAMEN. Without EL3 the enable is EL2's own and nothing sets it, so the cap stays off and a guest reaches the MPAM registers while ID_AA64PFR0_EL1.MPAM reads 0 for it.

Good spot.

Quoted 24 lines

Gate the traps on the ID registers alone. MPAMEN is not a term in any MPAM accessor, so they take effect without it. finalise_el2_state already wrote MPAM2_EL2 under the same condition, so MPAM3_EL3.TRAPLOWER is clear wherever the cap is set, and arm64.nompam still clears it.

Fixes 31ff96c38ea3 ("KVM: arm64: Fix missing traps of guest accesses to the MPAM registers")

---
Found this while working on the other MPAM thread [1].

[1] https://lore.kernel.org/all/CA+EHjTxeWxZiuSmnKLGLxTBXP4oJT7-LuffbPAyCSZZ5TW=5Ew@mail.gmail.com/

 arch/arm64/include/asm/cpufeature.h     |  5 +++++
 arch/arm64/kernel/cpufeature.c          | 13 +++++++++++++
 arch/arm64/kvm/hyp/include/hyp/switch.h |  4 ++--
 arch/arm64/tools/cpucaps                |  1 +
 4 files changed, 21 insertions(+), 2 deletions(-)
arch/arm64/include/asm/cpufeature.h +0 -0
index 7404a6e83a930..8863ae99596bc 100644

arch/arm64/include/asm/cpufeature.h static __always_inline bool system_supports_mpam_hcr(void) · line 879

Quoted 7 lines
arch/arm64/include/asm/cpufeature.h +4 -0
@@ -873,6 +873,11 @@ static __always_inline bool system_supports_mpam_hcr(void)
 	return alternative_has_cap_unlikely(ARM64_MPAM_HCR);
 }
 
+static __always_inline bool system_supports_mpam_sysregs(void)
+{
+	return alternative_has_cap_unlikely(ARM64_MPAM_SYSREGS);
+}

The sashiko comments reminded me about the possibility of mismatched systems. I see two cases to consider here. One is if the firmware doesn't touch the MPAM system registers and leaves MPAM3_EL3.TRAPLOWER set to 1. In which case the user is required to add arm64.nompam to the cmdline as the MPAM registers can't be accessed from EL2. The second is if the f/w clears MPAM3_EL3.TRAPLOWER, in which case arm64.nompam can't be used without making MPAM1_EL1 etc, shared between guests. Perhaps for these mismatched systems we need to unconditionally enable the EL2 traps for the cpus that support MPAM and not advertise any support for MPAM. Furthermore, if we, before kvm gets involved, unconditionally enable the EL2 traps on systems where MPAM can't be enabled then I'm not sure that we need to distinguish system_supports_mpam_sysregs() and system_supports_mpam() in the kvm code. What do you think? Does that fit in with the pattern of how cpu features are generally handled?

Thanks,

Ben

Quoted 71 lines
arch/arm64/include/asm/cpufeature.h +1 -0
+
 static inline bool system_supports_pmuv3(void)
 {
 	return cpus_have_final_cap(ARM64_HAS_PMUV3);
arch/arm64/kernel/cpufeature.c +13 -0
index 17b83a2518a8f..36a27692e5cf7 100644
@@ -2501,6 +2501,13 @@ test_has_mpam(const struct arm64_cpu_capabilities *entry, int scope)
 	return (read_sysreg_s(SYS_MPAM1_EL1) & MPAM1_EL1_MPAMEN);
 }
 
+static bool
+test_has_mpam_sysregs(const struct arm64_cpu_capabilities *entry, int __unused)
+{
+	/* The registers exist whether or not firmware enabled MPAM. */
+	return detect_ftr_has_mpam();
+}
+
 static void
 cpu_enable_mpam(const struct arm64_cpu_capabilities *entry)
 {
@@ -3116,6 +3123,12 @@ static const struct arm64_cpu_capabilities arm64_features[] = {
 		.matches = test_has_mpam,
 		.cpu_enable = cpu_enable_mpam,
 	},
+	{
+		.desc = "Memory Partitioning And Monitoring system registers",
+		.type = ARM64_CPUCAP_SYSTEM_FEATURE,
+		.capability = ARM64_MPAM_SYSREGS,
+		.matches = test_has_mpam_sysregs,
+	},
 	{
 		.desc = "Memory Partitioning And Monitoring Virtualisation",
 		.type = ARM64_CPUCAP_SYSTEM_FEATURE,
arch/arm64/kvm/hyp/include/hyp/switch.h +2 -2
index 1ce7130e25490..8941335724f6b 100644
@@ -298,7 +298,7 @@ static inline void  __activate_traps_mpam(struct kvm_vcpu *vcpu)
 	u64 clr = MPAM2_EL2_EnMPAMSM;
 	u64 set = MPAM2_EL2_TRAPMPAM0EL1 | MPAM2_EL2_TRAPMPAM1EL1;
 
-	if (!system_supports_mpam())
+	if (!system_supports_mpam_sysregs())
 		return;
 
 	/* trap guest access to MPAMIDR_EL1 */
@@ -317,7 +317,7 @@ static inline void __deactivate_traps_mpam(void)
 	u64 clr = MPAM2_EL2_TRAPMPAM0EL1 | MPAM2_EL2_TRAPMPAM1EL1 | MPAM2_EL2_TIDR;
 	u64 set = MPAM2_EL2_EnMPAMSM;
 
-	if (!system_supports_mpam())
+	if (!system_supports_mpam_sysregs())
 		return;
 
 	sysreg_clear_set_s(SYS_MPAM2_EL2, clr, set);
arch/arm64/tools/cpucaps +1 -0
index 2775ba3359cfe..aa5be51385f68 100644
@@ -78,6 +78,7 @@ KVM_PROTECTED_MODE
 MISMATCHED_CACHE_TYPE
 MPAM
 MPAM_HCR
+MPAM_SYSREGS
 MTE
 MTE_ASYMM
 MTE_FAR

base-commit: cee9395acd8043be0644b25c34bfa86623f2b935

author reviewer
#7 lore raw

Hi Ben,

On Mon, 7 Sept 2026 at 10:12, Ben Horgan <[email protected]> wrote:
...

Quoted 8 lines
Quoted 8 lines
arch/arm64/include/asm/cpufeature.h +0 -0
index 7404a6e83a930..8863ae99596bc 100644
@@ -873,6 +873,11 @@ static __always_inline bool system_supports_mpam_hcr(void)
      return alternative_has_cap_unlikely(ARM64_MPAM_HCR);
 }
 
Quoted 16 lines

arch/arm64/include/asm/cpufeature.h static __always_inline bool system_supports_mpam_hcr(void) · line 879

Quoted 4 lines
arch/arm64/include/asm/cpufeature.h +4 -0
+static __always_inline bool system_supports_mpam_sysregs(void)
+{
+     return alternative_has_cap_unlikely(ARM64_MPAM_SYSREGS);
+}

The sashiko comments reminded me about the possibility of mismatched systems. I see two cases to consider here. One is if the firmware doesn't touch the MPAM system registers and leaves MPAM3_EL3.TRAPLOWER set to 1. In which case the user is required to add arm64.nompam to the cmdline as the MPAM registers can't be accessed from EL2. The second is if the f/w clears MPAM3_EL3.TRAPLOWER, in which case arm64.nompam can't be used without making MPAM1_EL1 etc, shared between guests. Perhaps for these mismatched systems we need to unconditionally enable the EL2 traps for the cpus that support MPAM and not advertise any support for MPAM. Furthermore, if we, before kvm gets involved, unconditionally enable the EL2 traps on systems where MPAM can't be enabled then I'm not sure that we need to distinguish system_supports_mpam_sysregs() and system_supports_mpam() in the kvm code. What do you think? Does that fit in with the pattern of how cpu features are generally handled?

This patch misses that case. check_override reads the CPU's own ID registers, detect_ftr_has_mpam() the sanitised ones, so finalise_el2_state clears the traps and neither cap is set.

Not advertising MPAM already happens, since a mismatch lowers the ID field to 0. The trap default is the missing half, and I agree it belongs in finalise_el2_state, which already computes whether MPAM2_EL2 can be touched. KVM then needs only system_supports_mpam(), and the cap goes.

The one host left is a forced nVHE, where test_has_mpam()'s MPAM1_EL1 read would trap before cpu_enable_mpam(). The respin makes the caps false when is_hyp_nvhe(), so MPAM is off there rather than the boot hanging [1].

I'll respin along those lines.

Cheers,
/fuad

[1] https://lore.kernel.org/all/apqrky27mJmUV9UA@willie-the-truck/

Quoted 78 lines

Thanks,

Ben

Quoted 71 lines
arch/arm64/include/asm/cpufeature.h +1 -0
+
 static inline bool system_supports_pmuv3(void)
 {
      return cpus_have_final_cap(ARM64_HAS_PMUV3);
arch/arm64/kernel/cpufeature.c +13 -0
index 17b83a2518a8f..36a27692e5cf7 100644
@@ -2501,6 +2501,13 @@ test_has_mpam(const struct arm64_cpu_capabilities *entry, int scope)
      return (read_sysreg_s(SYS_MPAM1_EL1) & MPAM1_EL1_MPAMEN);
 }
 
+static bool
+test_has_mpam_sysregs(const struct arm64_cpu_capabilities *entry, int __unused)
+{
+     /* The registers exist whether or not firmware enabled MPAM. */
+     return detect_ftr_has_mpam();
+}
+
 static void
 cpu_enable_mpam(const struct arm64_cpu_capabilities *entry)
 {
@@ -3116,6 +3123,12 @@ static const struct arm64_cpu_capabilities arm64_features[] = {
              .matches = test_has_mpam,
              .cpu_enable = cpu_enable_mpam,
      },
+     {
+             .desc = "Memory Partitioning And Monitoring system registers",
+             .type = ARM64_CPUCAP_SYSTEM_FEATURE,
+             .capability = ARM64_MPAM_SYSREGS,
+             .matches = test_has_mpam_sysregs,
+     },
      {
              .desc = "Memory Partitioning And Monitoring Virtualisation",
              .type = ARM64_CPUCAP_SYSTEM_FEATURE,
arch/arm64/kvm/hyp/include/hyp/switch.h +2 -2
index 1ce7130e25490..8941335724f6b 100644
@@ -298,7 +298,7 @@ static inline void  __activate_traps_mpam(struct kvm_vcpu *vcpu)
      u64 clr = MPAM2_EL2_EnMPAMSM;
      u64 set = MPAM2_EL2_TRAPMPAM0EL1 | MPAM2_EL2_TRAPMPAM1EL1;
 
-     if (!system_supports_mpam())
+     if (!system_supports_mpam_sysregs())
              return;
 
      /* trap guest access to MPAMIDR_EL1 */
@@ -317,7 +317,7 @@ static inline void __deactivate_traps_mpam(void)
      u64 clr = MPAM2_EL2_TRAPMPAM0EL1 | MPAM2_EL2_TRAPMPAM1EL1 | MPAM2_EL2_TIDR;
      u64 set = MPAM2_EL2_EnMPAMSM;
 
-     if (!system_supports_mpam())
+     if (!system_supports_mpam_sysregs())
              return;
 
      sysreg_clear_set_s(SYS_MPAM2_EL2, clr, set);
arch/arm64/tools/cpucaps +1 -0
index 2775ba3359cfe..aa5be51385f68 100644
@@ -78,6 +78,7 @@ KVM_PROTECTED_MODE
 MISMATCHED_CACHE_TYPE
 MPAM
 MPAM_HCR
+MPAM_SYSREGS
 MTE
 MTE_ASYMM
 MTE_FAR

base-commit: cee9395acd8043be0644b25c34bfa86623f2b935

author reviewer
#8 lore raw

Hi Ben,

I built the respin first, and it didn't hold up.

On Mon, 7 Sept 2026 at 11:28, Fuad Tabba <[email protected]> wrote:
...

Quoted 9 lines

This patch misses that case. check_override reads the CPU's own ID registers, detect_ftr_has_mpam() the sanitised ones, so finalise_el2_state clears the traps and neither cap is set.

Not advertising MPAM already happens, since a mismatch lowers the ID field to 0. The trap default is the missing half, and I agree it belongs in finalise_el2_state, which already computes whether MPAM2_EL2 can be touched. KVM then needs only system_supports_mpam(), and the cap goes.

finalise_el2_state can't set that default. Under nVHE and hVHE the host is the trapped party, and cpufeature reads MPAMIDR_EL1 and MPAM1_EL1 from EL1 before KVM exists, so the only way out is to stop the host detecting MPAM.

Quoted 4 lines

The one host left is a forced nVHE, where test_has_mpam()'s MPAM1_EL1 read would trap before cpu_enable_mpam(). The respin makes the caps false when is_hyp_nvhe(), so MPAM is off there rather than the boot hanging [1].

is_hyp_nvhe() is true on an hVHE host too, since is_kernel_in_hyp_mode() reads CurrentEL. So that turns MPAM off for every pKVM host on VHE hardware, not for a forced-nVHE corner. On the AEM FVP with MPAM, a host booted kvm-arm.mode=protected detects MPAM without the change and not with it.

Quoted 1 line

I'll respin along those lines.

Not along those lines :) I think that v2 would be v1 with a clearer commit message, keeping both predicates. Merging them would give resctrl the wrong answer, since it needs firmware to have enabled MPAM where KVM's trapping needs only that the registers are implemented.

The mismatched machine stays uncovered, with this patch at least. Closing it means deciding per CPU at EL2 from the CPU's own registers rather than from a system-wide cap.

Cheers,
/fuad

Quoted 84 lines

Cheers,
/fuad

[1] https://lore.kernel.org/all/apqrky27mJmUV9UA@willie-the-truck/

Quoted 78 lines

Thanks,

Ben

Quoted 71 lines
arch/arm64/include/asm/cpufeature.h +1 -0
+
 static inline bool system_supports_pmuv3(void)
 {
      return cpus_have_final_cap(ARM64_HAS_PMUV3);
arch/arm64/kernel/cpufeature.c +13 -0
index 17b83a2518a8f..36a27692e5cf7 100644
@@ -2501,6 +2501,13 @@ test_has_mpam(const struct arm64_cpu_capabilities *entry, int scope)
      return (read_sysreg_s(SYS_MPAM1_EL1) & MPAM1_EL1_MPAMEN);
 }
 
+static bool
+test_has_mpam_sysregs(const struct arm64_cpu_capabilities *entry, int __unused)
+{
+     /* The registers exist whether or not firmware enabled MPAM. */
+     return detect_ftr_has_mpam();
+}
+
 static void
 cpu_enable_mpam(const struct arm64_cpu_capabilities *entry)
 {
@@ -3116,6 +3123,12 @@ static const struct arm64_cpu_capabilities arm64_features[] = {
              .matches = test_has_mpam,
              .cpu_enable = cpu_enable_mpam,
      },
+     {
+             .desc = "Memory Partitioning And Monitoring system registers",
+             .type = ARM64_CPUCAP_SYSTEM_FEATURE,
+             .capability = ARM64_MPAM_SYSREGS,
+             .matches = test_has_mpam_sysregs,
+     },
      {
              .desc = "Memory Partitioning And Monitoring Virtualisation",
              .type = ARM64_CPUCAP_SYSTEM_FEATURE,
arch/arm64/kvm/hyp/include/hyp/switch.h +2 -2
index 1ce7130e25490..8941335724f6b 100644
@@ -298,7 +298,7 @@ static inline void  __activate_traps_mpam(struct kvm_vcpu *vcpu)
      u64 clr = MPAM2_EL2_EnMPAMSM;
      u64 set = MPAM2_EL2_TRAPMPAM0EL1 | MPAM2_EL2_TRAPMPAM1EL1;
 
-     if (!system_supports_mpam())
+     if (!system_supports_mpam_sysregs())
              return;
 
      /* trap guest access to MPAMIDR_EL1 */
@@ -317,7 +317,7 @@ static inline void __deactivate_traps_mpam(void)
      u64 clr = MPAM2_EL2_TRAPMPAM0EL1 | MPAM2_EL2_TRAPMPAM1EL1 | MPAM2_EL2_TIDR;
      u64 set = MPAM2_EL2_EnMPAMSM;
 
-     if (!system_supports_mpam())
+     if (!system_supports_mpam_sysregs())
              return;
 
      sysreg_clear_set_s(SYS_MPAM2_EL2, clr, set);
arch/arm64/tools/cpucaps +1 -0
index 2775ba3359cfe..aa5be51385f68 100644
@@ -78,6 +78,7 @@ KVM_PROTECTED_MODE
 MISMATCHED_CACHE_TYPE
 MPAM
 MPAM_HCR
+MPAM_SYSREGS
 MTE
 MTE_ASYMM
 MTE_FAR

base-commit: cee9395acd8043be0644b25c34bfa86623f2b935

#9 lore raw

Hi Fuad,

Quoted 34 lines · Fuad Tabba

Hi Ben,

I built the respin first, and it didn't hold up.

On Mon, 7 Sept 2026 at 11:28, Fuad Tabba <[email protected]> wrote:
...

Quoted 9 lines

This patch misses that case. check_override reads the CPU's own ID registers, detect_ftr_has_mpam() the sanitised ones, so finalise_el2_state clears the traps and neither cap is set.

Not advertising MPAM already happens, since a mismatch lowers the ID field to 0. The trap default is the missing half, and I agree it belongs in finalise_el2_state, which already computes whether MPAM2_EL2 can be touched. KVM then needs only system_supports_mpam(), and the cap goes.

finalise_el2_state can't set that default. Under nVHE and hVHE the host is the trapped party, and cpufeature reads MPAMIDR_EL1 and MPAM1_EL1 from EL1 before KVM exists, so the only way out is to stop the host detecting MPAM.

Quoted 4 lines

The one host left is a forced nVHE, where test_has_mpam()'s MPAM1_EL1 read would trap before cpu_enable_mpam(). The respin makes the caps false when is_hyp_nvhe(), so MPAM is off there rather than the boot hanging [1].

is_hyp_nvhe() is true on an hVHE host too, since is_kernel_in_hyp_mode() reads CurrentEL. So that turns MPAM off for every pKVM host on VHE hardware, not for a forced-nVHE corner. On the AEM FVP with MPAM, a host booted kvm-arm.mode=protected detects MPAM without the change and not with it.

Quoted 1 line

I'll respin along those lines.

Quoted 8 lines

Not along those lines :) I think that v2 would be v1 with a clearer commit message, keeping both predicates. Merging them would give resctrl the wrong answer, since it needs firmware to have enabled MPAM where KVM's trapping needs only that the registers are implemented.

The mismatched machine stays uncovered, with this patch at least. Closing it means deciding per CPU at EL2 from the CPU's own registers rather than from a system-wide cap.

Ok, there seems no way to properly handle mismatched systems anyway as CPUs can come online at any time. I don't know of any mismatched MPAM systems and they would be strange as the interface to the interconnect would on the CPUS that support MPAM require providing PARTID and PMG and the others not.

Thanks,

Ben

Quoted 88 lines

Cheers,
/fuad

Quoted 84 lines

Cheers,
/fuad

[1] https://lore.kernel.org/all/apqrky27mJmUV9UA@willie-the-truck/

Quoted 78 lines

Thanks,

Ben

Quoted 71 lines
arch/arm64/include/asm/cpufeature.h +1 -0
+
 static inline bool system_supports_pmuv3(void)
 {
      return cpus_have_final_cap(ARM64_HAS_PMUV3);
arch/arm64/kernel/cpufeature.c +13 -0
index 17b83a2518a8f..36a27692e5cf7 100644
@@ -2501,6 +2501,13 @@ test_has_mpam(const struct arm64_cpu_capabilities *entry, int scope)
      return (read_sysreg_s(SYS_MPAM1_EL1) & MPAM1_EL1_MPAMEN);
 }
 
+static bool
+test_has_mpam_sysregs(const struct arm64_cpu_capabilities *entry, int __unused)
+{
+     /* The registers exist whether or not firmware enabled MPAM. */
+     return detect_ftr_has_mpam();
+}
+
 static void
 cpu_enable_mpam(const struct arm64_cpu_capabilities *entry)
 {
@@ -3116,6 +3123,12 @@ static const struct arm64_cpu_capabilities arm64_features[] = {
              .matches = test_has_mpam,
              .cpu_enable = cpu_enable_mpam,
      },
+     {
+             .desc = "Memory Partitioning And Monitoring system registers",
+             .type = ARM64_CPUCAP_SYSTEM_FEATURE,
+             .capability = ARM64_MPAM_SYSREGS,
+             .matches = test_has_mpam_sysregs,
+     },
      {
              .desc = "Memory Partitioning And Monitoring Virtualisation",
              .type = ARM64_CPUCAP_SYSTEM_FEATURE,
arch/arm64/kvm/hyp/include/hyp/switch.h +2 -2
index 1ce7130e25490..8941335724f6b 100644
@@ -298,7 +298,7 @@ static inline void  __activate_traps_mpam(struct kvm_vcpu *vcpu)
      u64 clr = MPAM2_EL2_EnMPAMSM;
      u64 set = MPAM2_EL2_TRAPMPAM0EL1 | MPAM2_EL2_TRAPMPAM1EL1;
 
-     if (!system_supports_mpam())
+     if (!system_supports_mpam_sysregs())
              return;
 
      /* trap guest access to MPAMIDR_EL1 */
@@ -317,7 +317,7 @@ static inline void __deactivate_traps_mpam(void)
      u64 clr = MPAM2_EL2_TRAPMPAM0EL1 | MPAM2_EL2_TRAPMPAM1EL1 | MPAM2_EL2_TIDR;
      u64 set = MPAM2_EL2_EnMPAMSM;
 
-     if (!system_supports_mpam())
+     if (!system_supports_mpam_sysregs())
              return;
 
      sysreg_clear_set_s(SYS_MPAM2_EL2, clr, set);
arch/arm64/tools/cpucaps +1 -0
index 2775ba3359cfe..aa5be51385f68 100644
@@ -78,6 +78,7 @@ KVM_PROTECTED_MODE
 MISMATCHED_CACHE_TYPE
 MPAM
 MPAM_HCR
+MPAM_SYSREGS
 MTE
 MTE_ASYMM
 MTE_FAR

base-commit: cee9395acd8043be0644b25c34bfa86623f2b935