Skip to contentSkip to conversation

arm64/boot: Disable trapping of PMZR_EL0 writes to EL2

PATCH Applied Fuad Tabba 4 participants 6 messages Sep 22 – Sep 23

Subsystems (3)
lore
Share & tools
lore

Fuad Tabba

author reviewer
#0 lore raw

PATCH 1/1 arm64/boot: Disable trapping of PMZR_EL0 writes to EL2

__init_el2_fgt2() writes one mask to both HDFGRTR2_EL2 and HDFGWTR2_EL2. PMZR_EL0 is write-only, so its trap bit, nPMZR_EL0, exists only in HDFGWTR2_EL2 and is therefore never set: a PMZR_EL0 write from the host traps to EL2, where the nVHE hypervisor has no handler and BUG()s. The kernel never writes PMZR_EL0, but kernel.perf_user_access=1 has the PMU driver set PMUSERENR_EL0.UEN for a task with a user-read event, so a write from EL0 reaches the trap and takes the host down without a panic message.

Accumulate the HDFGWTR2_EL2 bits separately, as __init_el2_fgt() already does for HDFGWTR_EL2, and set nPMZR_EL0 with the other FEAT_PMUv3p9 bits.

Fixes 858c7bfcb35e1 ("arm64/boot: Enable EL2 requirements for FEAT_PMUv3p9")

---
Based on Linux 7.3-rc4 (93f51579e7df2).

 Documentation/arch/arm64/booting.rst | 1 +
 arch/arm64/include/asm/el2_setup.h   | 9 ++++++++-
 2 files changed, 9 insertions(+), 1 deletion(-)
Documentation/arch/arm64/booting.rst +1 -0
index 13ef311dace83..3fea4b14ef7c2 100644
@@ -465,6 +465,7 @@ Before jumping into the kernel, the following conditions must be met:
     - HDFGWTR2_EL2.nPMICNTR_EL0 (bit 2) must be initialised to 0b1.
     - HDFGWTR2_EL2.nPMICFILTR_EL0 (bit 3) must be initialised to 0b1.
     - HDFGWTR2_EL2.nPMUACR_EL1 (bit 4) must be initialised to 0b1.
+    - HDFGWTR2_EL2.nPMZR_EL0 (bit 21) must be initialised to 0b1.
 
   For CPUs with SPE data source filtering (FEAT_SPE_FDS):
 
arch/arm64/include/asm/el2_setup.h +8 -1
index aa8ec9df80243..87560d8b254e6 100644
@@ -418,6 +418,7 @@
 	b.lt	.Lskip_fgt2_\@
 
 	mov	x0, xzr
+	mov	x2, xzr
 	mrs	x1, id_aa64dfr0_el1
 	ubfx	x1, x1, #ID_AA64DFR0_EL1_PMUVer_SHIFT, #4
 	cmp	x1, #ID_AA64DFR0_EL1_PMUVer_V3P9
@@ -426,6 +427,11 @@
 	orr	x0, x0, #HDFGRTR2_EL2_nPMICNTR_EL0
 	orr	x0, x0, #HDFGRTR2_EL2_nPMICFILTR_EL0
 	orr	x0, x0, #HDFGRTR2_EL2_nPMUACR_EL1
+	orr	x2, x2, #HDFGWTR2_EL2_nPMICNTR_EL0
+	orr	x2, x2, #HDFGWTR2_EL2_nPMICFILTR_EL0
+	orr	x2, x2, #HDFGWTR2_EL2_nPMUACR_EL1
+	/* PMZR_EL0 is write-only, so it has no read trap to disable */
+	orr	x2, x2, #HDFGWTR2_EL2_nPMZR_EL0
 .Lskip_pmuv3p9_\@:
 	/* If SPE is implemented, */
 	__spe_vers_imp .Lskip_spefds_\@, ID_AA64DFR0_EL1_PMSVer_IMP, x1
@@ -436,10 +442,11 @@
 	cbz	x1, .Lskip_spefds_\@
 	/* disable traps of PMSDSFR to EL2. */
 	orr	x0, x0, #HDFGRTR2_EL2_nPMSDSFR_EL1
+	orr	x2, x2, #HDFGWTR2_EL2_nPMSDSFR_EL1
 
 .Lskip_spefds_\@:
 	msr_s   SYS_HDFGRTR2_EL2, x0
-	msr_s   SYS_HDFGWTR2_EL2, x0
+	msr_s   SYS_HDFGWTR2_EL2, x2
 	msr_s   SYS_HFGRTR2_EL2, xzr
 	msr_s   SYS_HFGWTR2_EL2, xzr
 	msr_s   SYS_HFGITR2_EL2, xzr

base-commit: 93f51579e7df248780214094418f205253383cc5

Signature · 1 line
2.39.5
maintainer
#1 lore raw

Hi Fuad,

Review line 434

Quoted 9 lines · Fuad Tabba
(quoted diff) +5 -0
@@ -426,6 +427,11 @@
 	orr	x0, x0, #HDFGRTR2_EL2_nPMICNTR_EL0
 	orr	x0, x0, #HDFGRTR2_EL2_nPMICFILTR_EL0
 	orr	x0, x0, #HDFGRTR2_EL2_nPMUACR_EL1
+	orr	x2, x2, #HDFGWTR2_EL2_nPMICNTR_EL0
+	orr	x2, x2, #HDFGWTR2_EL2_nPMICFILTR_EL0
+	orr	x2, x2, #HDFGWTR2_EL2_nPMUACR_EL1
+	/* PMZR_EL0 is write-only, so it has no read trap to disable */
+	orr	x2, x2, #HDFGWTR2_EL2_nPMZR_EL0

We can trim 3 instructions from this by using the read-trap bits as the starting point for the write-trap bits, like below. It also makes clear the point that most of our trap bits are symmetrical for read and write.

Thanks,
Oliver

arch/arm64/include/asm/el2_setup.h +5 -1
index aa8ec9df8024..6c5d2cc4e6fa 100644
@@ -418,6 +418,7 @@
 	b.lt	.Lskip_fgt2_\@
 
 	mov	x0, xzr
+	mov	x2, xzr
 	mrs	x1, id_aa64dfr0_el1
 	ubfx	x1, x1, #ID_AA64DFR0_EL1_PMUVer_SHIFT, #4
 	cmp	x1, #ID_AA64DFR0_EL1_PMUVer_V3P9
@@ -426,6 +427,9 @@
 	orr	x0, x0, #HDFGRTR2_EL2_nPMICNTR_EL0
 	orr	x0, x0, #HDFGRTR2_EL2_nPMICFILTR_EL0
 	orr	x0, x0, #HDFGRTR2_EL2_nPMUACR_EL1
+
+	/* PMZR_EL0 is write-only, so it has no read trap to disable */
+	orr	x2, x0, #HDFGWTR2_EL2_nPMZR_EL0
 .Lskip_pmuv3p9_\@:
 	/* If SPE is implemented, */
 	__spe_vers_imp .Lskip_spefds_\@, ID_AA64DFR0_EL1_PMSVer_IMP, x1
@@ -439,7 +443,7 @@
 
 .Lskip_spefds_\@:
 	msr_s   SYS_HDFGRTR2_EL2, x0
-	msr_s   SYS_HDFGWTR2_EL2, x0
+	msr_s   SYS_HDFGWTR2_EL2, x2
 	msr_s   SYS_HFGRTR2_EL2, xzr
 	msr_s   SYS_HFGWTR2_EL2, xzr
 	msr_s   SYS_HFGITR2_EL2, xzr
author reviewer
#2 lore raw

Hi Oliver,

On Tue, 22 Sep 2026 20:47:58 +0100, Oliver Upton <[email protected]> wrote:
[...]

Quoted 3 lines

We can trim 3 instructions from this by using the read-trap bits as the starting point for the write-trap bits, like below. It also makes clear the point that most of our trap bits are symmetrical for read and write.

I did consider that, and went the other way for two reasons.

The read side has three bits with no write-side counterpart (nPMSSDATA, nSPMDEVAFF_EL1, nSPMID), so copying x0 into x2 writes ones into three RES0 bits of HDFGWTR2_EL2. Harmless today, but it's this bug in reverse: the first revision that defines one of those positions in the write register turns the copy into a silent behaviour change. Building each mask from its own register's names can't do that.

It also matches __init_el2_fgt, which already keeps x0 and x2 apart for HDFGRTR_EL2 and HDFGWTR_EL2. And even in that form the copy would have to sit just before the msr_s, after the SPE block, or nPMSDSFR_EL1 only lands in x0.

Cheers,
/fuad

maintainer Reviewed-by
#3 lore raw
Quoted 8 lines · Fuad Tabba

Hi Oliver,

On Tue, 22 Sep 2026 20:47:58 +0100, Oliver Upton <[email protected]> wrote:
[...]

Quoted 3 lines

We can trim 3 instructions from this by using the read-trap bits as the starting point for the write-trap bits, like below. It also makes clear the point that most of our trap bits are symmetrical for read and write.

Quoted 8 lines

I did consider that, and went the other way for two reasons.

The read side has three bits with no write-side counterpart (nPMSSDATA, nSPMDEVAFF_EL1, nSPMID), so copying x0 into x2 writes ones into three RES0 bits of HDFGWTR2_EL2. Harmless today, but it's this bug in reverse: the first revision that defines one of those positions in the write register turns the copy into a silent behaviour change. Building each mask from its own register's names can't do that.

My thinking was that we could have a third block, but...

Quoted 4 lines

It also matches __init_el2_fgt, which already keeps x0 and x2 apart for HDFGRTR_EL2 and HDFGWTR_EL2. And even in that form the copy would have to sit just before the msr_s, after the SPE block, or nPMSDSFR_EL1 only lands in x0.

new feature-specific initialization blocks will make this an absolute mess. So I agree, this is the right fix.

Best,
Oliver

Reviewed-by on v1 1/1
#4 lore raw
In reply to Fuad Tabba
Quoted 9 lines · Fuad Tabba

__init_el2_fgt2() writes one mask to both HDFGRTR2_EL2 and HDFGWTR2_EL2. PMZR_EL0 is write-only, so its trap bit, nPMZR_EL0, exists only in HDFGWTR2_EL2 and is therefore never set: a PMZR_EL0 write from the host traps to EL2, where the nVHE hypervisor has no handler and BUG()s. The kernel never writes PMZR_EL0, but kernel.perf_user_access=1 has the PMU driver set PMUSERENR_EL0.UEN for a task with a user-read event, so a write from EL0 reaches the trap and takes the host down without a panic message.

Quoted 7 lines

Accumulate the HDFGWTR2_EL2 bits separately, as __init_el2_fgt() already does for HDFGWTR_EL2, and set nPMZR_EL0 with the other FEAT_PMUv3p9 bits.

Fixes 858c7bfcb35e1 ("arm64/boot: Enable EL2 requirements for FEAT_PMUv3p9")
Quoted 61 lines

---
Based on Linux 7.3-rc4 (93f51579e7df2).

 Documentation/arch/arm64/booting.rst | 1 +
 arch/arm64/include/asm/el2_setup.h   | 9 ++++++++-
 2 files changed, 9 insertions(+), 1 deletion(-)
Documentation/arch/arm64/booting.rst +1 -0
index 13ef311dace83..3fea4b14ef7c2 100644
@@ -465,6 +465,7 @@ Before jumping into the kernel, the following conditions must be met:
     - HDFGWTR2_EL2.nPMICNTR_EL0 (bit 2) must be initialised to 0b1.
     - HDFGWTR2_EL2.nPMICFILTR_EL0 (bit 3) must be initialised to 0b1.
     - HDFGWTR2_EL2.nPMUACR_EL1 (bit 4) must be initialised to 0b1.
+    - HDFGWTR2_EL2.nPMZR_EL0 (bit 21) must be initialised to 0b1.
 
   For CPUs with SPE data source filtering (FEAT_SPE_FDS):
 
arch/arm64/include/asm/el2_setup.h +8 -1
index aa8ec9df80243..87560d8b254e6 100644
@@ -418,6 +418,7 @@
 	b.lt	.Lskip_fgt2_\@
 
 	mov	x0, xzr
+	mov	x2, xzr
 	mrs	x1, id_aa64dfr0_el1
 	ubfx	x1, x1, #ID_AA64DFR0_EL1_PMUVer_SHIFT, #4
 	cmp	x1, #ID_AA64DFR0_EL1_PMUVer_V3P9
@@ -426,6 +427,11 @@
 	orr	x0, x0, #HDFGRTR2_EL2_nPMICNTR_EL0
 	orr	x0, x0, #HDFGRTR2_EL2_nPMICFILTR_EL0
 	orr	x0, x0, #HDFGRTR2_EL2_nPMUACR_EL1
+	orr	x2, x2, #HDFGWTR2_EL2_nPMICNTR_EL0
+	orr	x2, x2, #HDFGWTR2_EL2_nPMICFILTR_EL0
+	orr	x2, x2, #HDFGWTR2_EL2_nPMUACR_EL1
+	/* PMZR_EL0 is write-only, so it has no read trap to disable */
+	orr	x2, x2, #HDFGWTR2_EL2_nPMZR_EL0
 .Lskip_pmuv3p9_\@:
 	/* If SPE is implemented, */
 	__spe_vers_imp .Lskip_spefds_\@, ID_AA64DFR0_EL1_PMSVer_IMP, x1
@@ -436,10 +442,11 @@
 	cbz	x1, .Lskip_spefds_\@
 	/* disable traps of PMSDSFR to EL2. */
 	orr	x0, x0, #HDFGRTR2_EL2_nPMSDSFR_EL1
+	orr	x2, x2, #HDFGWTR2_EL2_nPMSDSFR_EL1
 
 .Lskip_spefds_\@:
 	msr_s   SYS_HDFGRTR2_EL2, x0
-	msr_s   SYS_HDFGWTR2_EL2, x0
+	msr_s   SYS_HDFGWTR2_EL2, x2
 	msr_s   SYS_HFGRTR2_EL2, xzr
 	msr_s   SYS_HFGWTR2_EL2, xzr
 	msr_s   SYS_HFGITR2_EL2, xzr

base-commit: 93f51579e7df248780214094418f205253383cc5

Signature · 1 line
2.39.5
maintainer Applied
#5 lore raw
In reply to Fuad Tabba
Quoted 10 lines · Fuad Tabba

__init_el2_fgt2() writes one mask to both HDFGRTR2_EL2 and HDFGWTR2_EL2. PMZR_EL0 is write-only, so its trap bit, nPMZR_EL0, exists only in HDFGWTR2_EL2 and is therefore never set: a PMZR_EL0 write from the host traps to EL2, where the nVHE hypervisor has no handler and BUG()s. The kernel never writes PMZR_EL0, but kernel.perf_user_access=1 has the PMU driver set PMUSERENR_EL0.UEN for a task with a user-read event, so a write from EL0 reaches the trap and takes the host down without a panic message.

[...]

Applied to arm64 (for-next/fixes), thanks!

[1/1] arm64/boot: Disable trapping of PMZR_EL0 writes to EL2

      https://git.kernel.org/arm64/c/2bc6b218717b

Cheers,

Signature · 5 lines
Will

https://fixes.arm64.dev
https://next.arm64.dev
https://will.arm64.dev