[PATCH 3/3] perf: arm_spe: Add support for SPE VM interface
From: James Clark
Date: Tue Jul 01 2025 - 11:39:46 EST
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@xxxxxxxxxx>
---
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