Skip to contentSkip to conversation

perf/arm_pmuv3: Add PMUv3.9 per counter EL0 access control

PATCH Applied Rob Herring (Arm) 2 participants 5 messages Oct 2, 2024 – Oct 28, 2024

Subsystems (3)
lore
Share & tools
lore
#0 lore raw

PATCH 1/1 perf/arm_pmuv3: Add PMUv3.9 per counter EL0 access control

Armv8.9/9.4 PMUv3.9 adds per counter EL0 access controls. Per counter access is enabled with the UEN bit in PMUSERENR_EL1 register. Individual counters are enabled/disabled in the PMUACR_EL1 register. When UEN is set, the CR/ER bits control EL0 write access and must be set to disable write access.

With the access controls, the clearing of unused counters can be skipped.

KVM also configures PMUSERENR_EL1 in order to trap to EL2. UEN does not need to be set for it since only PMUv3.5 is exposed to guests.

---
 arch/arm/include/asm/arm_pmuv3.h   |  6 ++++++
 arch/arm64/include/asm/arm_pmuv3.h | 10 ++++++++++
 arch/arm64/tools/sysreg            |  8 ++++++++
 drivers/perf/arm_pmuv3.c           | 29 +++++++++++++++++++----------
 include/linux/perf/arm_pmuv3.h     |  1 +
 5 files changed, 44 insertions(+), 10 deletions(-)
arch/arm/include/asm/arm_pmuv3.h +6 -0
index f63ba8986b24..d242b5e1ca0d 100644
@@ -231,6 +231,7 @@ static inline void kvm_vcpu_pmu_resync_el0(void) {}
 #define ARMV8_PMU_DFR_VER_V3P1      0x4
 #define ARMV8_PMU_DFR_VER_V3P4      0x5
 #define ARMV8_PMU_DFR_VER_V3P5      0x6
+#define ARMV8_PMU_DFR_VER_V3P9      0x9
 #define ARMV8_PMU_DFR_VER_IMP_DEF   0xF
 
 static inline bool pmuv3_implemented(int pmuver)
@@ -249,6 +250,11 @@ static inline bool is_pmuv3p5(int pmuver)
 	return pmuver >= ARMV8_PMU_DFR_VER_V3P5;
 }
 
+static inline bool is_pmuv3p9(int pmuver)
+{
+	return pmuver >= ARMV8_PMU_DFR_VER_V3P9;
+}
+
 static inline u64 read_pmceid0(void)
 {
 	u64 val = read_sysreg(PMCEID0);
arch/arm64/include/asm/arm_pmuv3.h +10 -0
index 468a049bc63b..8a777dec8d88 100644
@@ -152,6 +152,11 @@ static inline void write_pmuserenr(u32 val)
 	write_sysreg(val, pmuserenr_el0);
 }
 
+static inline void write_pmuacr(u64 val)
+{
+	write_sysreg_s(val, SYS_PMUACR_EL1);
+}
+
 static inline u64 read_pmceid0(void)
 {
 	return read_sysreg(pmceid0_el0);
@@ -178,4 +183,9 @@ static inline bool is_pmuv3p5(int pmuver)
 	return pmuver >= ID_AA64DFR0_EL1_PMUVer_V3P5;
 }
 
+static inline bool is_pmuv3p9(int pmuver)
+{
+	return pmuver >= ID_AA64DFR0_EL1_PMUVer_V3P9;
+}
+
 #endif
arch/arm64/tools/sysreg +8 -0
index 8d637ac4b7c6..74fb5af91d4f 100644
@@ -1238,6 +1238,7 @@ UnsignedEnum	11:8	PMUVer
 	0b0110	V3P5
 	0b0111	V3P7
 	0b1000	V3P8
+	0b1001	V3P9
 	0b1111	IMP_DEF
 EndEnum
 UnsignedEnum	7:4	TraceVer
@@ -2178,6 +2179,13 @@ Field	4	P
 Field	3:0	ALIGN
 EndSysreg
 
+Sysreg	PMUACR_EL1	3	0	9	14	4
+Res0	63:33
+Field	32	F0
+Field	31	C
+Field	30:0	P
+EndSysreg
+
 Sysreg	PMSELR_EL0	3	3	9	12	5
 Res0	63:5
 Field	4:0	SEL
drivers/perf/arm_pmuv3.c +19 -10
index 0afe02f879b4..bb93d32b86ea 100644
@@ -770,18 +770,27 @@ static void armv8pmu_enable_user_access(struct arm_pmu *cpu_pmu)
 	int i;
 	struct pmu_hw_events *cpuc = this_cpu_ptr(cpu_pmu->hw_events);
 
-	/* Clear any unused counters to avoid leaking their contents */
-	for_each_andnot_bit(i, cpu_pmu->cntr_mask, cpuc->used_mask,
-			    ARMPMU_MAX_HWEVENTS) {
-		if (i == ARMV8_PMU_CYCLE_IDX)
-			write_pmccntr(0);
-		else if (i == ARMV8_PMU_INSTR_IDX)
-			write_pmicntr(0);
-		else
-			armv8pmu_write_evcntr(i, 0);
+	if (is_pmuv3p9(cpu_pmu->pmuver)) {
+		u64 mask = 0;
+		for_each_set_bit(i, cpuc->used_mask, ARMPMU_MAX_HWEVENTS) {
+			if (armv8pmu_event_has_user_read(cpuc->events[i]))
+				mask |= BIT(i);
+		}
+		write_pmuacr(mask);
+	} else {
+		/* Clear any unused counters to avoid leaking their contents */
+		for_each_andnot_bit(i, cpu_pmu->cntr_mask, cpuc->used_mask,
+				    ARMPMU_MAX_HWEVENTS) {
+			if (i == ARMV8_PMU_CYCLE_IDX)
+				write_pmccntr(0);
+			else if (i == ARMV8_PMU_INSTR_IDX)
+				write_pmicntr(0);
+			else
+				armv8pmu_write_evcntr(i, 0);
+		}
 	}
 
-	update_pmuserenr(ARMV8_PMU_USERENR_ER | ARMV8_PMU_USERENR_CR);
+	update_pmuserenr(ARMV8_PMU_USERENR_ER | ARMV8_PMU_USERENR_CR | ARMV8_PMU_USERENR_UEN);
 }
 
 static void armv8pmu_enable_event(struct perf_event *event)
include/linux/perf/arm_pmuv3.h +1 -0
index 3372c1b56486..d698efba28a2 100644
@@ -257,6 +257,7 @@
 #define ARMV8_PMU_USERENR_SW	(1 << 1) /* PMSWINC can be written at EL0 */
 #define ARMV8_PMU_USERENR_CR	(1 << 2) /* Cycle counter can be read at EL0 */
 #define ARMV8_PMU_USERENR_ER	(1 << 3) /* Event counter can be read at EL0 */
+#define ARMV8_PMU_USERENR_UEN	(1 << 4) /* Fine grained per counter access at EL0 */
 /* Mask for writable bits */
 #define ARMV8_PMU_USERENR_MASK	(ARMV8_PMU_USERENR_EN | ARMV8_PMU_USERENR_SW | \
 				 ARMV8_PMU_USERENR_CR | ARMV8_PMU_USERENR_ER)
Signature · 1 line
2.45.2
maintainer
#1 lore raw
Quoted 111 lines · Rob Herring (Arm)

Armv8.9/9.4 PMUv3.9 adds per counter EL0 access controls. Per counter access is enabled with the UEN bit in PMUSERENR_EL1 register. Individual counters are enabled/disabled in the PMUACR_EL1 register. When UEN is set, the CR/ER bits control EL0 write access and must be set to disable write access.

With the access controls, the clearing of unused counters can be skipped.

KVM also configures PMUSERENR_EL1 in order to trap to EL2. UEN does not need to be set for it since only PMUv3.5 is exposed to guests.

---
 arch/arm/include/asm/arm_pmuv3.h   |  6 ++++++
 arch/arm64/include/asm/arm_pmuv3.h | 10 ++++++++++
 arch/arm64/tools/sysreg            |  8 ++++++++
 drivers/perf/arm_pmuv3.c           | 29 +++++++++++++++++++----------
 include/linux/perf/arm_pmuv3.h     |  1 +
 5 files changed, 44 insertions(+), 10 deletions(-)
arch/arm/include/asm/arm_pmuv3.h +6 -0
index f63ba8986b24..d242b5e1ca0d 100644
@@ -231,6 +231,7 @@ static inline void kvm_vcpu_pmu_resync_el0(void) {}
 #define ARMV8_PMU_DFR_VER_V3P1      0x4
 #define ARMV8_PMU_DFR_VER_V3P4      0x5
 #define ARMV8_PMU_DFR_VER_V3P5      0x6
+#define ARMV8_PMU_DFR_VER_V3P9      0x9
 #define ARMV8_PMU_DFR_VER_IMP_DEF   0xF
 
 static inline bool pmuv3_implemented(int pmuver)
@@ -249,6 +250,11 @@ static inline bool is_pmuv3p5(int pmuver)
 	return pmuver >= ARMV8_PMU_DFR_VER_V3P5;
 }
 
+static inline bool is_pmuv3p9(int pmuver)
+{
+	return pmuver >= ARMV8_PMU_DFR_VER_V3P9;
+}
+
 static inline u64 read_pmceid0(void)
 {
 	u64 val = read_sysreg(PMCEID0);
arch/arm64/include/asm/arm_pmuv3.h +10 -0
index 468a049bc63b..8a777dec8d88 100644
@@ -152,6 +152,11 @@ static inline void write_pmuserenr(u32 val)
 	write_sysreg(val, pmuserenr_el0);
 }
 
+static inline void write_pmuacr(u64 val)
+{
+	write_sysreg_s(val, SYS_PMUACR_EL1);
+}
+
 static inline u64 read_pmceid0(void)
 {
 	return read_sysreg(pmceid0_el0);
@@ -178,4 +183,9 @@ static inline bool is_pmuv3p5(int pmuver)
 	return pmuver >= ID_AA64DFR0_EL1_PMUVer_V3P5;
 }
 
+static inline bool is_pmuv3p9(int pmuver)
+{
+	return pmuver >= ID_AA64DFR0_EL1_PMUVer_V3P9;
+}
+
 #endif
arch/arm64/tools/sysreg +8 -0
index 8d637ac4b7c6..74fb5af91d4f 100644
@@ -1238,6 +1238,7 @@ UnsignedEnum	11:8	PMUVer
 	0b0110	V3P5
 	0b0111	V3P7
 	0b1000	V3P8
+	0b1001	V3P9
 	0b1111	IMP_DEF
 EndEnum
 UnsignedEnum	7:4	TraceVer
@@ -2178,6 +2179,13 @@ Field	4	P
 Field	3:0	ALIGN
 EndSysreg
 
+Sysreg	PMUACR_EL1	3	0	9	14	4
+Res0	63:33
+Field	32	F0
+Field	31	C
+Field	30:0	P
+EndSysreg
+
 Sysreg	PMSELR_EL0	3	3	9	12	5
 Res0	63:5
 Field	4:0	SEL
drivers/perf/arm_pmuv3.c +0 -6
index 0afe02f879b4..bb93d32b86ea 100644
@@ -770,18 +770,27 @@ static void armv8pmu_enable_user_access(struct arm_pmu *cpu_pmu)
 	int i;
 	struct pmu_hw_events *cpuc = this_cpu_ptr(cpu_pmu->hw_events);
 
-	/* Clear any unused counters to avoid leaking their contents */
-	for_each_andnot_bit(i, cpu_pmu->cntr_mask, cpuc->used_mask,
-			    ARMPMU_MAX_HWEVENTS) {
-		if (i == ARMV8_PMU_CYCLE_IDX)
-			write_pmccntr(0);
-		else if (i == ARMV8_PMU_INSTR_IDX)

drivers/perf/arm_pmuv3.c static void armv8pmu_enable_user_access(struct arm_pmu *cpu_pmu) · line 779

Quoted 10 lines
drivers/perf/arm_pmuv3.c +7 -3
-			write_pmicntr(0);
-		else
-			armv8pmu_write_evcntr(i, 0);
+	if (is_pmuv3p9(cpu_pmu->pmuver)) {
+		u64 mask = 0;
+		for_each_set_bit(i, cpuc->used_mask, ARMPMU_MAX_HWEVENTS) {
+			if (armv8pmu_event_has_user_read(cpuc->events[i]))
+				mask |= BIT(i);
+		}
+		write_pmuacr(mask);

Since this is a new register, should we be zeroing it as part of our reset callback?

Will

#2 lore raw
Quoted 118 lines · Will Deacon
Quoted 116 lines · Rob Herring (Arm)

Armv8.9/9.4 PMUv3.9 adds per counter EL0 access controls. Per counter access is enabled with the UEN bit in PMUSERENR_EL1 register. Individual counters are enabled/disabled in the PMUACR_EL1 register. When UEN is set, the CR/ER bits control EL0 write access and must be set to disable write access.

With the access controls, the clearing of unused counters can be skipped.

KVM also configures PMUSERENR_EL1 in order to trap to EL2. UEN does not need to be set for it since only PMUv3.5 is exposed to guests.

---
 arch/arm/include/asm/arm_pmuv3.h   |  6 ++++++
 arch/arm64/include/asm/arm_pmuv3.h | 10 ++++++++++
 arch/arm64/tools/sysreg            |  8 ++++++++
 drivers/perf/arm_pmuv3.c           | 29 +++++++++++++++++++----------
 include/linux/perf/arm_pmuv3.h     |  1 +
 5 files changed, 44 insertions(+), 10 deletions(-)
arch/arm/include/asm/arm_pmuv3.h +6 -0
index f63ba8986b24..d242b5e1ca0d 100644
@@ -231,6 +231,7 @@ static inline void kvm_vcpu_pmu_resync_el0(void) {}
 #define ARMV8_PMU_DFR_VER_V3P1      0x4
 #define ARMV8_PMU_DFR_VER_V3P4      0x5
 #define ARMV8_PMU_DFR_VER_V3P5      0x6
+#define ARMV8_PMU_DFR_VER_V3P9      0x9
 #define ARMV8_PMU_DFR_VER_IMP_DEF   0xF
 
 static inline bool pmuv3_implemented(int pmuver)
@@ -249,6 +250,11 @@ static inline bool is_pmuv3p5(int pmuver)
      return pmuver >= ARMV8_PMU_DFR_VER_V3P5;
 }
 
+static inline bool is_pmuv3p9(int pmuver)
+{
+     return pmuver >= ARMV8_PMU_DFR_VER_V3P9;
+}
+
 static inline u64 read_pmceid0(void)
 {
      u64 val = read_sysreg(PMCEID0);
arch/arm64/include/asm/arm_pmuv3.h +10 -0
index 468a049bc63b..8a777dec8d88 100644
@@ -152,6 +152,11 @@ static inline void write_pmuserenr(u32 val)
      write_sysreg(val, pmuserenr_el0);
 }
 
+static inline void write_pmuacr(u64 val)
+{
+     write_sysreg_s(val, SYS_PMUACR_EL1);
+}
+
 static inline u64 read_pmceid0(void)
 {
      return read_sysreg(pmceid0_el0);
@@ -178,4 +183,9 @@ static inline bool is_pmuv3p5(int pmuver)
      return pmuver >= ID_AA64DFR0_EL1_PMUVer_V3P5;
 }
 
+static inline bool is_pmuv3p9(int pmuver)
+{
+     return pmuver >= ID_AA64DFR0_EL1_PMUVer_V3P9;
+}
+
 #endif
arch/arm64/tools/sysreg +8 -0
index 8d637ac4b7c6..74fb5af91d4f 100644
@@ -1238,6 +1238,7 @@ UnsignedEnum    11:8    PMUVer
      0b0110  V3P5
      0b0111  V3P7
      0b1000  V3P8
+     0b1001  V3P9
      0b1111  IMP_DEF
 EndEnum
 UnsignedEnum 7:4     TraceVer
@@ -2178,6 +2179,13 @@ Field  4       P
 Field        3:0     ALIGN
 EndSysreg
 
+Sysreg       PMUACR_EL1      3       0       9       14      4
+Res0 63:33
+Field        32      F0
+Field        31      C
+Field        30:0    P
+EndSysreg
+
 Sysreg       PMSELR_EL0      3       3       9       12      5
 Res0 63:5
 Field        4:0     SEL
drivers/perf/arm_pmuv3.c +2 -9
index 0afe02f879b4..bb93d32b86ea 100644
@@ -770,18 +770,27 @@ static void armv8pmu_enable_user_access(struct arm_pmu *cpu_pmu)
      int i;
      struct pmu_hw_events *cpuc = this_cpu_ptr(cpu_pmu->hw_events);
 
-     /* Clear any unused counters to avoid leaking their contents */
-     for_each_andnot_bit(i, cpu_pmu->cntr_mask, cpuc->used_mask,
-                         ARMPMU_MAX_HWEVENTS) {
-             if (i == ARMV8_PMU_CYCLE_IDX)
-                     write_pmccntr(0);
-             else if (i == ARMV8_PMU_INSTR_IDX)
-                     write_pmicntr(0);
-             else
-                     armv8pmu_write_evcntr(i, 0);
+     if (is_pmuv3p9(cpu_pmu->pmuver)) {
+             u64 mask = 0;
Quoted 8 lines

drivers/perf/arm_pmuv3.c static void armv8pmu_enable_user_access(struct arm_pmu *cpu_pmu) · line 779

Quoted 5 lines
drivers/perf/arm_pmuv3.c +5 -0
+             for_each_set_bit(i, cpuc->used_mask, ARMPMU_MAX_HWEVENTS) {
+                     if (armv8pmu_event_has_user_read(cpuc->events[i]))
+                             mask |= BIT(i);
+             }
+             write_pmuacr(mask);

Since this is a new register, should we be zeroing it as part of our reset callback?

That should not be necessary since EL0 access is gated off in PMUSEREN in general and enabling this register additionally requires setting the UEN bit. That's only done right after this.

But if the policy is to initialize all registers with unknown reset state, then yes, I can add that.

Rob

maintainer
#3 lore raw
Quoted 30 lines · Rob Herring
Quoted 20 lines · Will Deacon
Quoted 19 lines · Rob Herring (Arm)
drivers/perf/arm_pmuv3.c +2 -9
index 0afe02f879b4..bb93d32b86ea 100644
@@ -770,18 +770,27 @@ static void armv8pmu_enable_user_access(struct arm_pmu *cpu_pmu)
      int i;
      struct pmu_hw_events *cpuc = this_cpu_ptr(cpu_pmu->hw_events);
 
-     /* Clear any unused counters to avoid leaking their contents */
-     for_each_andnot_bit(i, cpu_pmu->cntr_mask, cpuc->used_mask,
-                         ARMPMU_MAX_HWEVENTS) {
-             if (i == ARMV8_PMU_CYCLE_IDX)
-                     write_pmccntr(0);
-             else if (i == ARMV8_PMU_INSTR_IDX)
-                     write_pmicntr(0);
-             else
-                     armv8pmu_write_evcntr(i, 0);
+     if (is_pmuv3p9(cpu_pmu->pmuver)) {
+             u64 mask = 0;
Quoted 8 lines

drivers/perf/arm_pmuv3.c static void armv8pmu_enable_user_access(struct arm_pmu *cpu_pmu) · line 779

Quoted 5 lines
drivers/perf/arm_pmuv3.c +5 -0
+             for_each_set_bit(i, cpuc->used_mask, ARMPMU_MAX_HWEVENTS) {
+                     if (armv8pmu_event_has_user_read(cpuc->events[i]))
+                             mask |= BIT(i);
+             }
+             write_pmuacr(mask);

Since this is a new register, should we be zeroing it as part of our reset callback?

Quoted 3 lines

That should not be necessary since EL0 access is gated off in PMUSEREN in general and enabling this register additionally requires setting the UEN bit. That's only done right after this.

That bit is set unconditionally, but it looks like we always write pmuacr for v3p9 parts so it should be ok. Thanks.

Will

maintainer Applied
#4 lore raw
In reply to Rob Herring (Arm)
Quoted 10 lines · Rob Herring (Arm)

Armv8.9/9.4 PMUv3.9 adds per counter EL0 access controls. Per counter access is enabled with the UEN bit in PMUSERENR_EL1 register. Individual counters are enabled/disabled in the PMUACR_EL1 register. When UEN is set, the CR/ER bits control EL0 write access and must be set to disable write access.

With the access controls, the clearing of unused counters can be skipped.

[...]

Applied to will (for-next/perf), thanks!

[1/1] perf/arm_pmuv3: Add PMUv3.9 per counter EL0 access control

      https://git.kernel.org/will/c/0bbff9ed8165

Cheers,

Signature · 5 lines
Will

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