Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

drivers/perf: apple_m1: Support host/guest event filtering

The PMU appears to have a separate register for filtering 'guest'
exception levels (i.e. EL1 and !ELIsInHost(EL0)) which has the same
layout as PMCR1_EL1. Conveniently, there exists a VHE register alias
(PMCR1_EL12) that can be used to configure it.

Support guest events by programming the EL12 register with the intended
guest kernel/userspace filters. Limit support for guest events to VHE
(i.e. kernel running at EL2), as it avoids involving KVM to context
switch PMU registers. VHE is the only supported mode on M* parts anyway,
so this isn't an actual feature limitation.

Tested-by: Janne Grunau <j@jannau.net>
Reviewed-by: Marc Zyngier <maz@kernel.org>
Link: https://lore.kernel.org/r/20250305202641.428114-3-oliver.upton@linux.dev
Signed-off-by: Oliver Upton <oliver.upton@linux.dev>

+17 -4
+1
arch/arm64/include/asm/apple_m1_pmu.h
··· 37 37 #define PMCR0_PMI_ENABLE_8_9 GENMASK(45, 44) 38 38 39 39 #define SYS_IMP_APL_PMCR1_EL1 sys_reg(3, 1, 15, 1, 0) 40 + #define SYS_IMP_APL_PMCR1_EL12 sys_reg(3, 1, 15, 7, 2) 40 41 #define PMCR1_COUNT_A64_EL0_0_7 GENMASK(15, 8) 41 42 #define PMCR1_COUNT_A64_EL1_0_7 GENMASK(23, 16) 42 43 #define PMCR1_COUNT_A64_EL0_8_9 GENMASK(41, 40)
+16 -4
drivers/perf/apple_m1_cpu_pmu.c
··· 120 120 */ 121 121 M1_PMU_CFG_COUNT_USER = BIT(8), 122 122 M1_PMU_CFG_COUNT_KERNEL = BIT(9), 123 + M1_PMU_CFG_COUNT_HOST = BIT(10), 124 + M1_PMU_CFG_COUNT_GUEST = BIT(11), 123 125 }; 124 126 125 127 /* ··· 330 328 } 331 329 332 330 static void __m1_pmu_configure_event_filter(unsigned int index, bool user, 333 - bool kernel) 331 + bool kernel, bool host) 334 332 { 335 333 u64 clear, set, user_bit, kernel_bit; 336 334 ··· 358 356 else 359 357 clear |= kernel_bit; 360 358 361 - sysreg_clear_set_s(SYS_IMP_APL_PMCR1_EL1, clear, set); 359 + if (host) 360 + sysreg_clear_set_s(SYS_IMP_APL_PMCR1_EL1, clear, set); 361 + else if (is_kernel_in_hyp_mode()) 362 + sysreg_clear_set_s(SYS_IMP_APL_PMCR1_EL12, clear, set); 362 363 } 363 364 364 365 static void __m1_pmu_configure_eventsel(unsigned int index, u8 event) ··· 396 391 static void m1_pmu_configure_counter(unsigned int index, unsigned long config_base) 397 392 { 398 393 bool kernel = config_base & M1_PMU_CFG_COUNT_KERNEL; 394 + bool guest = config_base & M1_PMU_CFG_COUNT_GUEST; 395 + bool host = config_base & M1_PMU_CFG_COUNT_HOST; 399 396 bool user = config_base & M1_PMU_CFG_COUNT_USER; 400 397 u8 evt = config_base & M1_PMU_CFG_EVENT; 401 398 402 - __m1_pmu_configure_event_filter(index, user, kernel); 399 + __m1_pmu_configure_event_filter(index, user && host, kernel && host, true); 400 + __m1_pmu_configure_event_filter(index, user && guest, kernel && guest, false); 403 401 __m1_pmu_configure_eventsel(index, evt); 404 402 } 405 403 ··· 578 570 { 579 571 unsigned long config_base = 0; 580 572 581 - if (!attr->exclude_guest) { 573 + if (!attr->exclude_guest && !is_kernel_in_hyp_mode()) { 582 574 pr_debug("ARM performance counters do not support mode exclusion\n"); 583 575 return -EOPNOTSUPP; 584 576 } ··· 586 578 config_base |= M1_PMU_CFG_COUNT_KERNEL; 587 579 if (!attr->exclude_user) 588 580 config_base |= M1_PMU_CFG_COUNT_USER; 581 + if (!attr->exclude_host) 582 + config_base |= M1_PMU_CFG_COUNT_HOST; 583 + if (!attr->exclude_guest) 584 + config_base |= M1_PMU_CFG_COUNT_GUEST; 589 585 590 586 event->config_base = config_base; 591 587