[PATCH 1/2] coresight: etm4x: Report whether the PMU counts external output 1
From: Amir Ayupov
Date: Thu Oct 01 2026 - 02:39:00 EST
cscfg features match on device class only, so a feature that drives ETM
external output 1 for the PMU to count as TRCEXTOUT1 (0x4011) would load
on every ETMv4 device, including CPUs whose PMU has no such event
(Neoverse N1) or whose PMU connection is undocumented. There the pulse is
never counted, and whatever depends on it silently does not happen.
Add a capability match flag, CS_CFG_MATCH_CAP_PMU_EXTOUT1, that features
can require. The ETM sets it at probe, on its own CPU, when:
- the CPU is one whose TRM documents ETE external outputs 0-3 as PMU
events TRCEXTOUT0-3: Cortex-A520, Cortex-X925 and Neoverse N2, N3, V2
and V3;
- PMUv3p1 or later is implemented and PMCEID0_EL0 reports TRCEXTOUT1,
which also covers a hypervisor hiding the event;
- the trace unit has event 1, since event 0 is the trace unit trigger,
which the TRBE driver treats as fatal.
The PMU only reports whether the event is implemented, not what drives
it, so the CPU list keeps the check conservative. Other cores can be
added once their manuals or hardware confirm the connection.
Matching on a flag skips the feature on other ETMs rather than failing
the load, which would unregister the device from cscfg and lose its
other configurations.
cscfg_csdev_enable_active_config() also returns success when the active
configuration is not loaded on the device, so a session selecting such
a configuration would run unconfigured. Return -EINVAL instead, so the
session fails to start on that CPU. This applies to all configurations.
Show the flag in the feature's configfs 'matches' attribute.
Assisted-by: Devmate:Opus-5.5
Signed-off-by: Amir Ayupov <aaupov@xxxxxx>
---
.../hwtracing/coresight/coresight-config.h | 2 +
.../hwtracing/coresight/coresight-etm4x-cfg.c | 7 ++-
.../coresight/coresight-etm4x-core.c | 44 +++++++++++++++++++
drivers/hwtracing/coresight/coresight-etm4x.h | 3 ++
.../coresight/coresight-syscfg-configfs.c | 3 ++
.../hwtracing/coresight/coresight-syscfg.c | 7 +++
6 files changed, 65 insertions(+), 1 deletion(-)
diff --git a/drivers/hwtracing/coresight/coresight-config.h b/drivers/hwtracing/coresight/coresight-config.h
index 90fd937d3bd83..2efe73a0d0a0d 100644
--- a/drivers/hwtracing/coresight/coresight-config.h
+++ b/drivers/hwtracing/coresight/coresight-config.h
@@ -29,6 +29,8 @@
*/
#define CS_CFG_MATCH_CLASS_SRC_ALL 0x0001 /* match any source */
#define CS_CFG_MATCH_CLASS_SRC_ETM4 0x0002 /* match any ETMv4 device */
+/* ETM external output 1 is countable by the PMU as TRCEXTOUT1 */
+#define CS_CFG_MATCH_CAP_PMU_EXTOUT1 0x0004
/* flags defining device instance matching - used in config match desc data. */
#define CS_CFG_MATCH_INST_ANY 0x80000000 /* any instance of a class */
diff --git a/drivers/hwtracing/coresight/coresight-etm4x-cfg.c b/drivers/hwtracing/coresight/coresight-etm4x-cfg.c
index e1a59b4345052..2847d2d7f7bed 100644
--- a/drivers/hwtracing/coresight/coresight-etm4x-cfg.c
+++ b/drivers/hwtracing/coresight/coresight-etm4x-cfg.c
@@ -174,9 +174,14 @@ static int etm4_cfg_load_feature(struct coresight_device *csdev,
int etm4_cscfg_register(struct coresight_device *csdev)
{
+ struct etmv4_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
struct cscfg_csdev_feat_ops ops;
+ u32 match_flags = CS_CFG_ETM4_MATCH_FLAGS;
ops.load_feat = &etm4_cfg_load_feature;
- return cscfg_register_csdev(csdev, CS_CFG_ETM4_MATCH_FLAGS, &ops);
+ if (drvdata->pmu_extout1)
+ match_flags |= CS_CFG_MATCH_CAP_PMU_EXTOUT1;
+
+ return cscfg_register_csdev(csdev, match_flags, &ops);
}
diff --git a/drivers/hwtracing/coresight/coresight-etm4x-core.c b/drivers/hwtracing/coresight/coresight-etm4x-core.c
index 2247ad55d4442..7dc29727f07db 100644
--- a/drivers/hwtracing/coresight/coresight-etm4x-core.c
+++ b/drivers/hwtracing/coresight/coresight-etm4x-core.c
@@ -30,6 +30,7 @@
#include <linux/uaccess.h>
#include <linux/perf_event.h>
#include <linux/perf/arm_pmu.h>
+#include <linux/perf/arm_pmuv3.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/property.h>
@@ -1368,6 +1369,47 @@ static void etm4_fixup_wrong_ccitmin(struct etmv4_drvdata *drvdata)
}
}
+/*
+ * CPUs whose Technical Reference Manuals document ETE external outputs 0-3 as
+ * the PMU common events TRCEXTOUT0-3.
+ */
+static const struct midr_range etm4_pmu_extout1_cpus[] = {
+ MIDR_ALL_VERSIONS(MIDR_CORTEX_A520),
+ MIDR_ALL_VERSIONS(MIDR_CORTEX_X925),
+ MIDR_ALL_VERSIONS(MIDR_NEOVERSE_N2),
+ MIDR_ALL_VERSIONS(MIDR_NEOVERSE_N3),
+ MIDR_ALL_VERSIONS(MIDR_NEOVERSE_V2),
+ MIDR_ALL_VERSIONS(MIDR_NEOVERSE_V3),
+ {},
+};
+
+/*
+ * Check whether a PMU event can count external output 1 of this trace unit,
+ * and whether the trace unit has event 1 to drive it, since event 0 is the
+ * trace unit trigger. The PMU only reports whether an event is implemented,
+ * not that the trace unit drives it, so also require a CPU whose manual
+ * documents the connection.
+ *
+ * Must run on the CPU that owns the trace unit.
+ */
+static bool etm4_pmu_has_extout1(struct etmv4_drvdata *drvdata)
+{
+ int pmuver = read_pmuver();
+
+ if (!is_midr_in_range_list(etm4_pmu_extout1_cpus))
+ return false;
+
+ /* PMCEID0_EL0[63:32] describe events 0x4000-0x401f from PMUv3p1 */
+ if (!pmuv3_implemented(pmuver) || pmuver < ID_AA64DFR0_EL1_PMUVer_V3P1)
+ return false;
+ if (!(read_pmceid0() & BIT_ULL(32 + ARMV8_PMUV3_PERFCTR_TRCEXTOUT1 -
+ ARMV8_PMUV3_EXT_COMMON_EVENT_BASE)))
+ return false;
+
+ /* nr_event is TRCIDR0.NUMEVENT, the number of events minus one */
+ return drvdata->nr_event >= 1;
+}
+
static void etm4_init_arch_data(void *info)
{
u32 etmidr0;
@@ -1522,6 +1564,8 @@ static void etm4_init_arch_data(void *info)
/* NUMCNTR, bits[30:28] number of counters available for tracing */
drvdata->nr_cntr = FIELD_GET(TRCIDR5_NUMCNTR_MASK, etmidr5);
+ drvdata->pmu_extout1 = etm4_pmu_has_extout1(drvdata);
+
coresight_clear_self_claim_tag_unlocked(csa);
etm4_cs_lock(drvdata, csa);
cpu_detect_trace_filtering(drvdata);
diff --git a/drivers/hwtracing/coresight/coresight-etm4x.h b/drivers/hwtracing/coresight/coresight-etm4x.h
index df9e0748d71ec..dbdfe48e8b6c3 100644
--- a/drivers/hwtracing/coresight/coresight-etm4x.h
+++ b/drivers/hwtracing/coresight/coresight-etm4x.h
@@ -1024,6 +1024,8 @@ struct etmv4_save_state {
* @skip_power_up: Indicates if an implementation can skip powering up
* the trace unit.
* @paused: Indicates if the trace unit is paused.
+ * @pmu_extout1: The PMU counts external output 1 of this trace unit as
+ * TRCEXTOUT1, and the trace unit has event 1 to drive it.
* @arch_features: Bitmap of arch features of etmv4 devices.
*/
struct etmv4_drvdata {
@@ -1076,6 +1078,7 @@ struct etmv4_drvdata {
bool lpoverride : 1;
bool skip_power_up : 1;
bool paused : 1;
+ bool pmu_extout1 : 1;
u64 trfcr;
struct etmv4_config config;
struct etmv4_save_state *save_state;
diff --git a/drivers/hwtracing/coresight/coresight-syscfg-configfs.c b/drivers/hwtracing/coresight/coresight-syscfg-configfs.c
index 2b40e556be872..cd446d7ca43da 100644
--- a/drivers/hwtracing/coresight/coresight-syscfg-configfs.c
+++ b/drivers/hwtracing/coresight/coresight-syscfg-configfs.c
@@ -250,6 +250,9 @@ static ssize_t cscfg_feat_matches_show(struct config_item *item, char *page)
if (match_flags & CS_CFG_MATCH_CLASS_SRC_ETM4)
used += scnprintf(page + used, PAGE_SIZE - used, "SRC_ETMV4 ");
+ if (match_flags & CS_CFG_MATCH_CAP_PMU_EXTOUT1)
+ used += scnprintf(page + used, PAGE_SIZE - used, "PMU_EXTOUT1 ");
+
used += scnprintf(page + used, PAGE_SIZE - used, "\n");
return used;
}
diff --git a/drivers/hwtracing/coresight/coresight-syscfg.c b/drivers/hwtracing/coresight/coresight-syscfg.c
index 2bfdd7b45e49c..953c060c88549 100644
--- a/drivers/hwtracing/coresight/coresight-syscfg.c
+++ b/drivers/hwtracing/coresight/coresight-syscfg.c
@@ -1116,6 +1116,13 @@ int cscfg_csdev_enable_active_config(struct coresight_device *csdev,
if (err)
cscfg_config_desc_put(config_desc);
+ } else {
+ /*
+ * The configuration is active but was not loaded on this
+ * device, for example because the device lacks a capability
+ * its features require. Fail rather than trace without it.
+ */
+ err = -EINVAL;
}
return err;
--
2.52.0