[PATCH 3/3] perf: arm_spe: Add support for SPE VM interface
From: James Clark <james.clark@linaro.org>
Date: 2025-07-01 15:33:03
Also in:
linux-perf-users, lkml
Subsystem:
arm pmu profiling and debugging, arm64 port (aarch64 architecture), the rest · Maintainers:
Will Deacon, Mark Rutland, Catalin Marinas, Linus Torvalds
DEN0154 describes the new SPE VM interface, which allows the hypervisor to define a max buffer size hint and to raise a new buffer management error for exceeding it. Report the size as a capability to userspace, and prevent larger buffers from being allocated in the driver. Although it's a only a hint and smaller buffers may also be disallowed in some scenarios, a larger buffer is never expected to work so we can fail early. Failures after arm_spe_pmu_setup_aux() have to happen asynchronously through the buffer management event. Signed-off-by: James Clark <james.clark@linaro.org> --- arch/arm64/include/asm/sysreg.h | 1 + arch/arm64/tools/sysreg | 6 +++++- drivers/perf/arm_spe_pmu.c | 26 ++++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/include/asm/sysreg.h b/arch/arm64/include/asm/sysreg.h
index f1bb0d10c39a..9c48a7119aa7 100644
--- a/arch/arm64/include/asm/sysreg.h
+++ b/arch/arm64/include/asm/sysreg.h@@ -367,6 +367,7 @@ #define PMBSR_EL1_BUF_BSC_MASK PMBSR_EL1_MSS_MASK #define PMBSR_EL1_BUF_BSC_FULL 0x1UL +#define PMBSR_EL1_BUF_BSC_SIZE 0x4UL /*** End of Statistical Profiling Extension ***/
diff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg
index 8a8cf6874298..d6bb1736f554 100644
--- a/arch/arm64/tools/sysreg
+++ b/arch/arm64/tools/sysreg@@ -2976,7 +2976,11 @@ Field 7:0 Attr EndSysreg Sysreg PMBIDR_EL1 3 0 9 10 7 -Res0 63:12 +Res0 63:48 +Field 47:46 MaxBuffSize_RES +Field 45:41 MaxBuffSize_E +Field 40:32 MaxBuffSize_M +Res0 31:12 Enum 11:8 EA 0b0000 NotDescribed 0b0001 Ignored
diff --git a/drivers/perf/arm_spe_pmu.c b/drivers/perf/arm_spe_pmu.c
index 5829947c8871..23f18dc2890c 100644
--- a/drivers/perf/arm_spe_pmu.c
+++ b/drivers/perf/arm_spe_pmu.c@@ -92,6 +92,7 @@ struct arm_spe_pmu { u16 max_record_sz; u16 align; struct perf_output_handle __percpu *handle; + u64 max_buff_size; }; #define to_spe_pmu(p) (container_of(p, struct arm_spe_pmu, pmu))
@@ -115,6 +116,7 @@ enum arm_spe_pmu_capabilities { SPE_PMU_CAP_FEAT_MAX, SPE_PMU_CAP_CNT_SZ = SPE_PMU_CAP_FEAT_MAX, SPE_PMU_CAP_MIN_IVAL, + SPE_PMU_CAP_MAX_BUFF_SIZE, }; static int arm_spe_pmu_feat_caps[SPE_PMU_CAP_FEAT_MAX] = {
@@ -132,6 +134,8 @@ static u32 arm_spe_pmu_cap_get(struct arm_spe_pmu *spe_pmu, int cap) return spe_pmu->counter_sz; case SPE_PMU_CAP_MIN_IVAL: return spe_pmu->min_period; + case SPE_PMU_CAP_MAX_BUFF_SIZE: + return spe_pmu->max_buff_size; default: WARN(1, "unknown cap %d\n", cap); }
@@ -164,6 +168,7 @@ static struct attribute *arm_spe_pmu_cap_attr[] = { SPE_CAP_EXT_ATTR_ENTRY(ernd, SPE_PMU_CAP_ERND), SPE_CAP_EXT_ATTR_ENTRY(count_size, SPE_PMU_CAP_CNT_SZ), SPE_CAP_EXT_ATTR_ENTRY(min_interval, SPE_PMU_CAP_MIN_IVAL), + SPE_CAP_EXT_ATTR_ENTRY(max_buff_size, SPE_PMU_CAP_MAX_BUFF_SIZE), NULL, };
@@ -631,6 +636,9 @@ arm_spe_pmu_buf_get_fault_act(struct perf_output_handle *handle) case PMBSR_EL1_BUF_BSC_FULL: ret = SPE_PMU_BUF_FAULT_ACT_OK; goto out_stop; + case PMBSR_EL1_BUF_BSC_SIZE: + err_str = "Buffer size too large"; + goto out_err; default: err_str = "Unknown buffer status code"; }
@@ -896,6 +904,7 @@ static void *arm_spe_pmu_setup_aux(struct perf_event *event, void **pages, int i, cpu = event->cpu; struct page **pglist; struct arm_spe_pmu_buf *buf; + struct arm_spe_pmu *spe_pmu = to_spe_pmu(event->pmu); /* We need at least two pages for this to work. */ if (nr_pages < 2)
@@ -910,6 +919,10 @@ static void *arm_spe_pmu_setup_aux(struct perf_event *event, void **pages, if (snapshot && (nr_pages & 1)) return NULL; + if (spe_pmu->max_buff_size && + nr_pages * PAGE_SIZE > spe_pmu->max_buff_size) + return NULL; + if (cpu == -1) cpu = raw_smp_processor_id();
@@ -999,6 +1012,17 @@ static void arm_spe_pmu_perf_destroy(struct arm_spe_pmu *spe_pmu) perf_pmu_unregister(&spe_pmu->pmu); } +static u64 arm_spe_decode_buf_size(u64 pmbidr) +{ + u64 mantissa = FIELD_GET(PMBIDR_EL1_MaxBuffSize_M, pmbidr); + u8 exp = FIELD_GET(PMBIDR_EL1_MaxBuffSize_E, pmbidr); + + if (exp == 0) + return mantissa << 12; + + return (mantissa | 0b1000000000) << (exp + 11); +} + static void __arm_spe_pmu_dev_probe(void *info) { int fld;
@@ -1033,6 +1057,8 @@ static void __arm_spe_pmu_dev_probe(void *info) return; } + spe_pmu->max_buff_size = arm_spe_decode_buf_size(reg); + /* It's now safe to read PMSIDR and figure out what we've got */ reg = read_sysreg_s(SYS_PMSIDR_EL1); if (FIELD_GET(PMSIDR_EL1_FE, reg))
--
2.34.1