* [PATCH v2 0/5] perf/arm-cmn: Support CMN S3 r2
@ 2026-07-28 15:59 Robin Murphy
2026-07-28 15:59 ` [PATCH v2 1/5] perf/arm-cmn: Rename filter variables for clarity Robin Murphy
` (4 more replies)
0 siblings, 5 replies; 9+ messages in thread
From: Robin Murphy @ 2026-07-28 15:59 UTC (permalink / raw)
To: will, mark.rutland; +Cc: linux-arm-kernel, linux-perf-users, ilkka, leo.yan
v1: https://lore.kernel.org/all/cover.1782830759.git.robin.murphy@arm.com/
Hi all,
Since nothing else has come up, this is a very minor respin to fix
the bisection nit that Leo spotted, and hopefully it's good to land?
Thanks,
Robin.
Robin Murphy (5):
perf/arm-cmn: Rename filter variables for clarity
perf/arm-cmn: Refactor event filter programming
perf/arm-cmn: Refactor event filter data
perf/arm-cmn: Plumb in new filters
perf/arm-cmn: Support CMN S3 r2
drivers/perf/arm-cmn.c | 490 +++++++++++++++++++++++++++++------------
1 file changed, 347 insertions(+), 143 deletions(-)
--
2.54.0.dirty
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v2 1/5] perf/arm-cmn: Rename filter variables for clarity
2026-07-28 15:59 [PATCH v2 0/5] perf/arm-cmn: Support CMN S3 r2 Robin Murphy
@ 2026-07-28 15:59 ` Robin Murphy
2026-07-28 15:59 ` [PATCH v2 2/5] perf/arm-cmn: Refactor event filter programming Robin Murphy
` (3 subsequent siblings)
4 siblings, 0 replies; 9+ messages in thread
From: Robin Murphy @ 2026-07-28 15:59 UTC (permalink / raw)
To: will, mark.rutland; +Cc: linux-arm-kernel, linux-perf-users, ilkka, leo.yan
CMN has already grown many more event-specific filters than the original
Occupancy ID, but since they are all independent of each other we've
just overloaded them onto the same name. Before we add yet more, and
they begin to overlap, rename all our "occupid" variables to "filter" so
that things can be a bit clearer and more consistent (but leaving the
format attribute itself, to avoid UAPI concerns).
Reviewed-by: Ilkka Koskinen <ilkka@os.amperecomputing.com>
Signed-off-by: Robin Murphy <robin.murphy@arm.com>
---
v2: No change
drivers/perf/arm-cmn.c | 53 ++++++++++++++++++++++--------------------
1 file changed, 28 insertions(+), 25 deletions(-)
diff --git a/drivers/perf/arm-cmn.c b/drivers/perf/arm-cmn.c
index 50402bc4a21d..2a8a67da72c3 100644
--- a/drivers/perf/arm-cmn.c
+++ b/drivers/perf/arm-cmn.c
@@ -163,13 +163,13 @@
/* Event attributes */
#define CMN_CONFIG_TYPE GENMASK_ULL(15, 0)
#define CMN_CONFIG_EVENTID GENMASK_ULL(26, 16)
-#define CMN_CONFIG_OCCUPID GENMASK_ULL(30, 27)
+#define CMN_CONFIG_FILTER GENMASK_ULL(30, 27)
#define CMN_CONFIG_BYNODEID BIT_ULL(31)
#define CMN_CONFIG_NODEID GENMASK_ULL(47, 32)
#define CMN_EVENT_TYPE(event) FIELD_GET(CMN_CONFIG_TYPE, (event)->attr.config)
#define CMN_EVENT_EVENTID(event) FIELD_GET(CMN_CONFIG_EVENTID, (event)->attr.config)
-#define CMN_EVENT_OCCUPID(event) FIELD_GET(CMN_CONFIG_OCCUPID, (event)->attr.config)
+#define CMN_EVENT_FILTER(event) FIELD_GET(CMN_CONFIG_FILTER, (event)->attr.config)
#define CMN_EVENT_BYNODEID(event) FIELD_GET(CMN_CONFIG_BYNODEID, (event)->attr.config)
#define CMN_EVENT_NODEID(event) FIELD_GET(CMN_CONFIG_NODEID, (event)->attr.config)
@@ -301,7 +301,7 @@ struct arm_cmn_node {
struct {
u8 val : 4;
u8 count : 4;
- } occupid[SEL_MAX];
+ } filter[SEL_MAX];
union {
u8 event[4];
__le32 event_sel;
@@ -672,7 +672,7 @@ struct arm_cmn_event_attr {
enum cmn_node_type type;
enum cmn_filter_select fsel;
u16 eventid;
- u8 occupid;
+ u8 filter;
};
struct arm_cmn_format_attr {
@@ -681,13 +681,13 @@ struct arm_cmn_format_attr {
int config;
};
-#define _CMN_EVENT_ATTR(_model, _name, _type, _eventid, _occupid, _fsel)\
+#define _CMN_EVENT_ATTR(_model, _name, _type, _eventid, _filter, _fsel)\
(&((struct arm_cmn_event_attr[]) {{ \
.attr = __ATTR(_name, 0444, arm_cmn_event_show, NULL), \
.model = _model, \
.type = _type, \
.eventid = _eventid, \
- .occupid = _occupid, \
+ .filter = _filter, \
.fsel = _fsel, \
}})[0].attr.attr)
#define CMN_EVENT_ATTR(_model, _name, _type, _eventid) \
@@ -709,8 +709,8 @@ static ssize_t arm_cmn_event_show(struct device *dev,
eattr->type, eattr->eventid);
if (eattr->fsel > SEL_NONE)
- return sysfs_emit(buf, "type=0x%x,eventid=0x%x,occupid=0x%x\n",
- eattr->type, eattr->eventid, eattr->occupid);
+ return sysfs_emit(buf, "type=0x%x,eventid=0x%x,filter=0x%x\n",
+ eattr->type, eattr->eventid, eattr->filter);
return sysfs_emit(buf, "type=0x%x,eventid=0x%x\n", eattr->type,
eattr->eventid);
@@ -1320,7 +1320,7 @@ static ssize_t arm_cmn_format_show(struct device *dev,
static struct attribute *arm_cmn_format_attrs[] = {
CMN_FORMAT_ATTR(type, CMN_CONFIG_TYPE),
CMN_FORMAT_ATTR(eventid, CMN_CONFIG_EVENTID),
- CMN_FORMAT_ATTR(occupid, CMN_CONFIG_OCCUPID),
+ CMN_FORMAT_ATTR(filter, CMN_CONFIG_FILTER),
CMN_FORMAT_ATTR(bynodeid, CMN_CONFIG_BYNODEID),
CMN_FORMAT_ATTR(nodeid, CMN_CONFIG_NODEID),
@@ -1333,6 +1333,9 @@ static struct attribute *arm_cmn_format_attrs[] = {
_CMN_FORMAT_ATTR(wp_val, 1, CMN_CONFIG1_WP_VAL),
_CMN_FORMAT_ATTR(wp_mask, 2, CMN_CONFIG2_WP_MASK),
+ /* Old name for UAPI compatibility */
+ CMN_FORMAT_ATTR(occupid, CMN_CONFIG_FILTER),
+
NULL
};
@@ -1544,30 +1547,30 @@ static void arm_cmn_event_read(struct perf_event *event)
}
static int arm_cmn_set_event_sel_hi(struct arm_cmn_node *dn,
- enum cmn_filter_select fsel, u8 occupid)
+ enum cmn_filter_select fsel, u8 filter)
{
u64 reg;
if (fsel == SEL_NONE)
return 0;
- if (!dn->occupid[fsel].count) {
- dn->occupid[fsel].val = occupid;
+ if (!dn->filter[fsel].count) {
+ dn->filter[fsel].val = filter;
reg = FIELD_PREP(CMN__PMU_CBUSY_SNTHROTTLE_SEL,
- dn->occupid[SEL_CBUSY_SNTHROTTLE_SEL].val) |
+ dn->filter[SEL_CBUSY_SNTHROTTLE_SEL].val) |
FIELD_PREP(CMN__PMU_SN_HOME_SEL,
- dn->occupid[SEL_SN_HOME_SEL].val) |
+ dn->filter[SEL_SN_HOME_SEL].val) |
FIELD_PREP(CMN__PMU_HBT_LBT_SEL,
- dn->occupid[SEL_HBT_LBT_SEL].val) |
+ dn->filter[SEL_HBT_LBT_SEL].val) |
FIELD_PREP(CMN__PMU_CLASS_OCCUP_ID,
- dn->occupid[SEL_CLASS_OCCUP_ID].val) |
+ dn->filter[SEL_CLASS_OCCUP_ID].val) |
FIELD_PREP(CMN__PMU_OCCUP1_ID,
- dn->occupid[SEL_OCCUP1ID].val);
+ dn->filter[SEL_OCCUP1ID].val);
writel_relaxed(reg >> 32, dn->pmu_base + CMN_PMU_EVENT_SEL + 4);
- } else if (dn->occupid[fsel].val != occupid) {
+ } else if (dn->filter[fsel].val != filter) {
return -EBUSY;
}
- dn->occupid[fsel].count++;
+ dn->filter[fsel].count++;
return 0;
}
@@ -1649,7 +1652,7 @@ static void arm_cmn_event_stop(struct perf_event *event, int flags)
struct arm_cmn_val {
u8 dtm_count[CMN_MAX_DTMS];
- u8 occupid[CMN_MAX_DTMS][SEL_MAX];
+ u8 filter[CMN_MAX_DTMS][SEL_MAX];
u8 wp[CMN_MAX_DTMS][4];
u8 wp_combine[CMN_MAX_DTMS][2];
int dtc_count[CMN_MAX_DTCS];
@@ -1694,7 +1697,7 @@ static void arm_cmn_val_add_event(struct arm_cmn *cmn, struct arm_cmn_val *val,
val->dtm_count[dtm]++;
if (sel > SEL_NONE)
- val->occupid[dtm][sel] = CMN_EVENT_OCCUPID(event) + 1;
+ val->filter[dtm][sel] = CMN_EVENT_FILTER(event) + 1;
if (type != CMN_TYPE_WP)
continue;
@@ -1745,8 +1748,8 @@ static int arm_cmn_validate_group(struct arm_cmn *cmn, struct perf_event *event)
if (val->dtm_count[dtm] == CMN_DTM_NUM_COUNTERS)
goto done;
- if (sel > SEL_NONE && val->occupid[dtm][sel] &&
- val->occupid[dtm][sel] != CMN_EVENT_OCCUPID(event) + 1)
+ if (sel > SEL_NONE && val->filter[dtm][sel] &&
+ val->filter[dtm][sel] != CMN_EVENT_FILTER(event) + 1)
goto done;
if (type != CMN_TYPE_WP)
@@ -1892,7 +1895,7 @@ static void arm_cmn_event_clear(struct arm_cmn *cmn, struct perf_event *event,
}
if (hw->filter_sel > SEL_NONE)
- hw->dn[i].occupid[hw->filter_sel].count--;
+ hw->dn[i].filter[hw->filter_sel].count--;
dtm->pmu_config_low &= ~CMN__PMEVCNT_PAIRED(dtm_idx);
writel_relaxed(dtm->pmu_config_low, dtm->base + CMN_DTM_PMU_CONFIG);
@@ -1978,7 +1981,7 @@ static int arm_cmn_event_add(struct perf_event *event, int flags)
input_sel = CMN__PMEVCNT0_INPUT_SEL_DEV + dtm_idx +
(nid.port << 4) + (nid.dev << 2);
- if (arm_cmn_set_event_sel_hi(dn, hw->filter_sel, CMN_EVENT_OCCUPID(event)))
+ if (arm_cmn_set_event_sel_hi(dn, hw->filter_sel, CMN_EVENT_FILTER(event)))
goto free_dtms;
}
--
2.54.0.dirty
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v2 2/5] perf/arm-cmn: Refactor event filter programming
2026-07-28 15:59 [PATCH v2 0/5] perf/arm-cmn: Support CMN S3 r2 Robin Murphy
2026-07-28 15:59 ` [PATCH v2 1/5] perf/arm-cmn: Rename filter variables for clarity Robin Murphy
@ 2026-07-28 15:59 ` Robin Murphy
2026-07-28 15:59 ` [PATCH v2 3/5] perf/arm-cmn: Refactor event filter data Robin Murphy
` (2 subsequent siblings)
4 siblings, 0 replies; 9+ messages in thread
From: Robin Murphy @ 2026-07-28 15:59 UTC (permalink / raw)
To: will, mark.rutland; +Cc: linux-arm-kernel, linux-perf-users, ilkka, leo.yan
We're soon going to need to cope with events having multiple filters,
plus the filter fields themselves moving around, wherein any more inline
if/else logic will struggle to scale. Add a more general abstraction for
the node-specific filter controls, and rejig the pmu_event_sel filter
programming around it in a more extensible manner.
Reviewed-by: Ilkka Koskinen <ilkka@os.amperecomputing.com>
Signed-off-by: Robin Murphy <robin.murphy@arm.com>
---
v2: No change
drivers/perf/arm-cmn.c | 139 ++++++++++++++++++++++++++++++-----------
1 file changed, 101 insertions(+), 38 deletions(-)
diff --git a/drivers/perf/arm-cmn.c b/drivers/perf/arm-cmn.c
index 2a8a67da72c3..14c267d2e2f9 100644
--- a/drivers/perf/arm-cmn.c
+++ b/drivers/perf/arm-cmn.c
@@ -278,8 +278,8 @@ enum cmn_node_type {
};
enum cmn_filter_select {
- SEL_NONE = -1,
- SEL_OCCUP1ID,
+ SEL_NONE,
+ SEL_OCCUP1_ID,
SEL_CLASS_OCCUP_ID,
SEL_CBUSY_SNTHROTTLE_SEL,
SEL_HBT_LBT_SEL,
@@ -599,6 +599,57 @@ static void arm_cmn_debugfs_init(struct arm_cmn *cmn, int id)
static void arm_cmn_debugfs_init(struct arm_cmn *cmn, int id) {}
#endif
+enum cmn_filter_type {
+ FILT_NONE,
+ FILT_OCCUP1_ID,
+ FILT_HNF_700,
+ FILT_HNS,
+};
+#define CMN_FILTER(_sel) [SEL_##_sel] = CMN__PMU_##_sel
+
+static const u64 arm_cmn_filters[][SEL_MAX] = {
+ [FILT_NONE] = {},
+ /* DVM etc. */
+ [FILT_OCCUP1_ID] = {
+ CMN_FILTER(OCCUP1_ID)
+ },
+ /* Newer HN-F */
+ [FILT_HNF_700] = {
+ CMN_FILTER(OCCUP1_ID),
+ CMN_FILTER(CLASS_OCCUP_ID),
+ CMN_FILTER(CBUSY_SNTHROTTLE_SEL)
+ },
+ /* HN-S */
+ [FILT_HNS] = {
+ CMN_FILTER(OCCUP1_ID),
+ CMN_FILTER(CLASS_OCCUP_ID),
+ CMN_FILTER(CBUSY_SNTHROTTLE_SEL),
+ CMN_FILTER(HBT_LBT_SEL),
+ CMN_FILTER(SN_HOME_SEL)
+ },
+};
+
+static enum cmn_filter_type arm_cmn_filter(enum cmn_node_type node,
+ enum cmn_model model)
+{
+ switch (node) {
+ default:
+ return FILT_NONE;
+ case CMN_TYPE_DVM:
+ case CMN_TYPE_CXRA:
+ case CMN_TYPE_CXHA:
+ case CMN_TYPE_CCRA:
+ case CMN_TYPE_CCHA:
+ return FILT_OCCUP1_ID;
+ case CMN_TYPE_HNF:
+ if (model < CMN700)
+ return FILT_OCCUP1_ID;
+ return FILT_HNF_700;
+ case CMN_TYPE_HNS:
+ return FILT_HNS;
+ };
+}
+
struct arm_cmn_hw_event {
struct arm_cmn_node *dn;
union {
@@ -708,7 +759,7 @@ static ssize_t arm_cmn_event_show(struct device *dev,
"type=0x%x,eventid=0x%x,wp_dev_sel=?,wp_chn_sel=?,wp_grp=?,wp_val=?,wp_mask=?\n",
eattr->type, eattr->eventid);
- if (eattr->fsel > SEL_NONE)
+ if (eattr->fsel)
return sysfs_emit(buf, "type=0x%x,eventid=0x%x,filter=0x%x\n",
eattr->type, eattr->eventid, eattr->filter);
@@ -832,16 +883,16 @@ static umode_t arm_cmn_event_attr_is_visible(struct kobject *kobj,
#define CMN_EVENT_DVM(_model, _name, _event) \
_CMN_EVENT_DVM(_model, _name, _event, 0, SEL_NONE)
#define CMN_EVENT_DVM_OCC(_model, _name, _event) \
- _CMN_EVENT_DVM(_model, _name##_all, _event, 0, SEL_OCCUP1ID), \
- _CMN_EVENT_DVM(_model, _name##_dvmop, _event, 1, SEL_OCCUP1ID), \
- _CMN_EVENT_DVM(_model, _name##_dvmsync, _event, 2, SEL_OCCUP1ID)
+ _CMN_EVENT_DVM(_model, _name##_all, _event, 0, SEL_OCCUP1_ID), \
+ _CMN_EVENT_DVM(_model, _name##_dvmop, _event, 1, SEL_OCCUP1_ID), \
+ _CMN_EVENT_DVM(_model, _name##_dvmsync, _event, 2, SEL_OCCUP1_ID)
#define CMN_EVENT_HN_OCC(_model, _name, _type, _event) \
- _CMN_EVENT_ATTR(_model, _name##_all, _type, _event, 0, SEL_OCCUP1ID), \
- _CMN_EVENT_ATTR(_model, _name##_read, _type, _event, 1, SEL_OCCUP1ID), \
- _CMN_EVENT_ATTR(_model, _name##_write, _type, _event, 2, SEL_OCCUP1ID), \
- _CMN_EVENT_ATTR(_model, _name##_atomic, _type, _event, 3, SEL_OCCUP1ID), \
- _CMN_EVENT_ATTR(_model, _name##_stash, _type, _event, 4, SEL_OCCUP1ID)
+ _CMN_EVENT_ATTR(_model, _name##_all, _type, _event, 0, SEL_OCCUP1_ID), \
+ _CMN_EVENT_ATTR(_model, _name##_read, _type, _event, 1, SEL_OCCUP1_ID), \
+ _CMN_EVENT_ATTR(_model, _name##_write, _type, _event, 2, SEL_OCCUP1_ID), \
+ _CMN_EVENT_ATTR(_model, _name##_atomic, _type, _event, 3, SEL_OCCUP1_ID), \
+ _CMN_EVENT_ATTR(_model, _name##_stash, _type, _event, 4, SEL_OCCUP1_ID)
#define CMN_EVENT_HN_CLS(_model, _name, _type, _event) \
_CMN_EVENT_ATTR(_model, _name##_class0, _type, _event, 0, SEL_CLASS_OCCUP_ID), \
_CMN_EVENT_ATTR(_model, _name##_class1, _type, _event, 1, SEL_CLASS_OCCUP_ID), \
@@ -865,9 +916,9 @@ static umode_t arm_cmn_event_attr_is_visible(struct kobject *kobj,
#define CMN_EVENT_HNS_OCC(_name, _event) \
CMN_EVENT_HN_OCC(CMN_ANY, hns_##_name, CMN_TYPE_HNS, _event), \
- _CMN_EVENT_ATTR(CMN_ANY, hns_##_name##_rxsnp, CMN_TYPE_HNS, _event, 5, SEL_OCCUP1ID), \
- _CMN_EVENT_ATTR(CMN_ANY, hns_##_name##_lbt, CMN_TYPE_HNS, _event, 6, SEL_OCCUP1ID), \
- _CMN_EVENT_ATTR(CMN_ANY, hns_##_name##_hbt, CMN_TYPE_HNS, _event, 7, SEL_OCCUP1ID)
+ _CMN_EVENT_ATTR(CMN_ANY, hns_##_name##_rxsnp, CMN_TYPE_HNS, _event, 5, SEL_OCCUP1_ID), \
+ _CMN_EVENT_ATTR(CMN_ANY, hns_##_name##_lbt, CMN_TYPE_HNS, _event, 6, SEL_OCCUP1_ID), \
+ _CMN_EVENT_ATTR(CMN_ANY, hns_##_name##_hbt, CMN_TYPE_HNS, _event, 7, SEL_OCCUP1_ID)
#define CMN_EVENT_HNS_CLS( _name, _event) \
CMN_EVENT_HN_CLS(CMN_ANY, hns_##_name, CMN_TYPE_HNS, _event)
#define CMN_EVENT_HNS_SNT(_name, _event) \
@@ -1547,27 +1598,20 @@ static void arm_cmn_event_read(struct perf_event *event)
}
static int arm_cmn_set_event_sel_hi(struct arm_cmn_node *dn,
- enum cmn_filter_select fsel, u8 filter)
+ enum cmn_filter_select fsel, u8 val)
{
- u64 reg;
-
- if (fsel == SEL_NONE)
- return 0;
-
if (!dn->filter[fsel].count) {
- dn->filter[fsel].val = filter;
- reg = FIELD_PREP(CMN__PMU_CBUSY_SNTHROTTLE_SEL,
- dn->filter[SEL_CBUSY_SNTHROTTLE_SEL].val) |
- FIELD_PREP(CMN__PMU_SN_HOME_SEL,
- dn->filter[SEL_SN_HOME_SEL].val) |
- FIELD_PREP(CMN__PMU_HBT_LBT_SEL,
- dn->filter[SEL_HBT_LBT_SEL].val) |
- FIELD_PREP(CMN__PMU_CLASS_OCCUP_ID,
- dn->filter[SEL_CLASS_OCCUP_ID].val) |
- FIELD_PREP(CMN__PMU_OCCUP1_ID,
- dn->filter[SEL_OCCUP1ID].val);
+ const u64 *filter = arm_cmn_filters[dn->filter[SEL_NONE].val];
+ u64 reg = 0;
+
+ dn->filter[fsel].val = val;
+ for (int i = SEL_OCCUP1_ID; i < SEL_MAX; i++) {
+ if (filter[i])
+ reg |= field_prep(filter[i], dn->filter[i].val);
+ }
+
writel_relaxed(reg >> 32, dn->pmu_base + CMN_PMU_EVENT_SEL + 4);
- } else if (dn->filter[fsel].val != filter) {
+ } else if (dn->filter[fsel].val != val) {
return -EBUSY;
}
dn->filter[fsel].count++;
@@ -1696,7 +1740,7 @@ static void arm_cmn_val_add_event(struct arm_cmn *cmn, struct arm_cmn_val *val,
val->dtm_count[dtm]++;
- if (sel > SEL_NONE)
+ if (sel)
val->filter[dtm][sel] = CMN_EVENT_FILTER(event) + 1;
if (type != CMN_TYPE_WP)
@@ -1748,7 +1792,7 @@ static int arm_cmn_validate_group(struct arm_cmn *cmn, struct perf_event *event)
if (val->dtm_count[dtm] == CMN_DTM_NUM_COUNTERS)
goto done;
- if (sel > SEL_NONE && val->filter[dtm][sel] &&
+ if (sel && val->filter[dtm][sel] &&
val->filter[dtm][sel] != CMN_EVENT_FILTER(event) + 1)
goto done;
@@ -1894,7 +1938,7 @@ static void arm_cmn_event_clear(struct arm_cmn *cmn, struct perf_event *event,
dtm->wp_event[wp_idx] = -1;
}
- if (hw->filter_sel > SEL_NONE)
+ if (hw->filter_sel)
hw->dn[i].filter[hw->filter_sel].count--;
dtm->pmu_config_low &= ~CMN__PMEVCNT_PAIRED(dtm_idx);
@@ -1906,6 +1950,17 @@ static void arm_cmn_event_clear(struct arm_cmn *cmn, struct perf_event *event,
cmn->dtc[j].counters[idx] = NULL;
}
+static int arm_cmn_set_event_filter(struct arm_cmn_node *dn, struct perf_event *event)
+{
+ enum cmn_filter_select fsel = to_cmn_hw(event)->filter_sel;
+ int ret = 0;
+
+ if (fsel)
+ ret = arm_cmn_set_event_sel_hi(dn, fsel, CMN_EVENT_FILTER(event));
+
+ return ret;
+}
+
static int arm_cmn_event_add(struct perf_event *event, int flags)
{
struct arm_cmn *cmn = to_cmn(event->pmu);
@@ -1981,7 +2036,7 @@ static int arm_cmn_event_add(struct perf_event *event, int flags)
input_sel = CMN__PMEVCNT0_INPUT_SEL_DEV + dtm_idx +
(nid.port << 4) + (nid.dev << 2);
- if (arm_cmn_set_event_sel_hi(dn, hw->filter_sel, CMN_EVENT_FILTER(event)))
+ if (arm_cmn_set_event_filter(dn, event))
goto free_dtms;
}
@@ -2311,6 +2366,7 @@ static int arm_cmn_discover(struct arm_cmn *cmn, unsigned int rgn_offset)
void __iomem *cfg_region, __iomem *xp_region;
struct arm_cmn_node cfg, *dn;
struct arm_cmn_dtm *dtm;
+ enum cmn_model model;
enum cmn_part part;
u16 child_count, child_poff;
u64 reg;
@@ -2342,12 +2398,14 @@ static int arm_cmn_discover(struct arm_cmn *cmn, unsigned int rgn_offset)
"Firmware binding mismatch: expected part number 0x%x, found 0x%x\n",
cmn->part, part);
cmn->part = part;
- if (!arm_cmn_model(cmn))
- dev_warn(cmn->dev, "Unknown part number: 0x%x\n", part);
reg = readl_relaxed(cfg_region + CMN_CFGM_PERIPH_ID_23);
cmn->rev = FIELD_GET(CMN_CFGM_PID2_REVISION, reg);
+ model = arm_cmn_model(cmn);
+ if (!model)
+ dev_warn(cmn->dev, "Unknown part number: 0x%x\n", part);
+
/*
* With the device isolation feature, if firmware has neglected to enable
* an XP port then we risk locking up if we try to access anything behind
@@ -2500,6 +2558,11 @@ static int arm_cmn_discover(struct arm_cmn *cmn, unsigned int rgn_offset)
dev_err(cmn->dev, "Node ID invalid for supported CMN versions: %d\n", dn->logid);
return -ENODEV;
}
+ /*
+ * We can utilise the "wasted" filter array slot to store
+ * the index for referencing the filter encodings later.
+ */
+ dn->filter[SEL_NONE].val = arm_cmn_filter(dn->type, model);
switch (dn->type) {
case CMN_TYPE_DTC:
--
2.54.0.dirty
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v2 3/5] perf/arm-cmn: Refactor event filter data
2026-07-28 15:59 [PATCH v2 0/5] perf/arm-cmn: Support CMN S3 r2 Robin Murphy
2026-07-28 15:59 ` [PATCH v2 1/5] perf/arm-cmn: Rename filter variables for clarity Robin Murphy
2026-07-28 15:59 ` [PATCH v2 2/5] perf/arm-cmn: Refactor event filter programming Robin Murphy
@ 2026-07-28 15:59 ` Robin Murphy
2026-07-28 15:59 ` [PATCH v2 4/5] perf/arm-cmn: Plumb in new filter types Robin Murphy
2026-07-28 15:59 ` [PATCH v2 5/5] perf/arm-cmn: Support CMN S3 r2 Robin Murphy
4 siblings, 0 replies; 9+ messages in thread
From: Robin Murphy @ 2026-07-28 15:59 UTC (permalink / raw)
To: will, mark.rutland; +Cc: linux-arm-kernel, linux-perf-users, ilkka, leo.yan
The ABI hole I have dug myself into requires the driver to know which
event encodings are associated with which particular filter control.
Since we will soon have a notion of multiple filters per event, refactor
the event data to encapsulate filters in an explicit structure, which
can then more easily scale as an array in future.
Signed-off-by: Robin Murphy <robin.murphy@arm.com>
---
v2: Avoid transient bug with filter.val in _CMN_EVENT_ATTR()
drivers/perf/arm-cmn.c | 95 ++++++++++++++++++++++--------------------
1 file changed, 49 insertions(+), 46 deletions(-)
diff --git a/drivers/perf/arm-cmn.c b/drivers/perf/arm-cmn.c
index 14c267d2e2f9..74bc2aae4a06 100644
--- a/drivers/perf/arm-cmn.c
+++ b/drivers/perf/arm-cmn.c
@@ -717,13 +717,17 @@ static void arm_cmn_clear_idx(struct arm_cmn_hw_event *hw)
bitmap_zero(hw->wp_idx, CMN_MAX_XPS);
}
+struct arm_cmn_filter_attr {
+ enum cmn_filter_select sel;
+ u8 val;
+};
+
struct arm_cmn_event_attr {
struct device_attribute attr;
enum cmn_model model;
enum cmn_node_type type;
- enum cmn_filter_select fsel;
u16 eventid;
- u8 filter;
+ struct arm_cmn_filter_attr filter[1];
};
struct arm_cmn_format_attr {
@@ -732,24 +736,25 @@ struct arm_cmn_format_attr {
int config;
};
-#define _CMN_EVENT_ATTR(_model, _name, _type, _eventid, _filter, _fsel)\
+#define _CMN_EVENT_ATTR(_model, _name, _type, _eventid, _fa, _fb, ...) \
(&((struct arm_cmn_event_attr[]) {{ \
.attr = __ATTR(_name, 0444, arm_cmn_event_show, NULL), \
.model = _model, \
.type = _type, \
.eventid = _eventid, \
- .filter = _filter, \
- .fsel = _fsel, \
+ .filter = {{_fa, _fb}}, \
}})[0].attr.attr)
-#define CMN_EVENT_ATTR(_model, _name, _type, _eventid) \
- _CMN_EVENT_ATTR(_model, _name, _type, _eventid, 0, SEL_NONE)
+#define CMN_EVENT_ATTR(_model, _name, _type, _eventid, _filter...) \
+ _CMN_EVENT_ATTR(_model, _name, _type, _eventid, ##_filter, 0, 0)
static ssize_t arm_cmn_event_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct arm_cmn_event_attr *eattr;
+ struct arm_cmn_filter_attr *filter;
eattr = container_of(attr, typeof(*eattr), attr);
+ filter = eattr->filter;
if (eattr->type == CMN_TYPE_DTC)
return sysfs_emit(buf, "type=0x%x\n", eattr->type);
@@ -759,9 +764,9 @@ static ssize_t arm_cmn_event_show(struct device *dev,
"type=0x%x,eventid=0x%x,wp_dev_sel=?,wp_chn_sel=?,wp_grp=?,wp_val=?,wp_mask=?\n",
eattr->type, eattr->eventid);
- if (eattr->fsel)
+ if (filter[0].sel)
return sysfs_emit(buf, "type=0x%x,eventid=0x%x,filter=0x%x\n",
- eattr->type, eattr->eventid, eattr->filter);
+ eattr->type, eattr->eventid, filter[0].val);
return sysfs_emit(buf, "type=0x%x,eventid=0x%x\n", eattr->type,
eattr->eventid);
@@ -849,8 +854,8 @@ static umode_t arm_cmn_event_attr_is_visible(struct kobject *kobj,
return attr->mode;
}
-#define _CMN_EVENT_DVM(_model, _name, _event, _occup, _fsel) \
- _CMN_EVENT_ATTR(_model, dn_##_name, CMN_TYPE_DVM, _event, _occup, _fsel)
+#define CMN_EVENT_DVM(_model, _name, _event, _filter...) \
+ CMN_EVENT_ATTR(_model, dn_##_name, CMN_TYPE_DVM, _event, ##_filter)
#define CMN_EVENT_DTC(_name) \
CMN_EVENT_ATTR(CMN_ANY, dtc_##_name, CMN_TYPE_DTC, 0)
#define CMN_EVENT_HNF(_model, _name, _event) \
@@ -880,32 +885,30 @@ static umode_t arm_cmn_event_attr_is_visible(struct kobject *kobj,
#define CMN_EVENT_HNS(_name, _event) \
CMN_EVENT_ATTR(CMN_ANY, hns_##_name, CMN_TYPE_HNS, _event)
-#define CMN_EVENT_DVM(_model, _name, _event) \
- _CMN_EVENT_DVM(_model, _name, _event, 0, SEL_NONE)
#define CMN_EVENT_DVM_OCC(_model, _name, _event) \
- _CMN_EVENT_DVM(_model, _name##_all, _event, 0, SEL_OCCUP1_ID), \
- _CMN_EVENT_DVM(_model, _name##_dvmop, _event, 1, SEL_OCCUP1_ID), \
- _CMN_EVENT_DVM(_model, _name##_dvmsync, _event, 2, SEL_OCCUP1_ID)
+ CMN_EVENT_DVM(_model, _name##_all, _event, SEL_OCCUP1_ID, 0), \
+ CMN_EVENT_DVM(_model, _name##_dvmop, _event, SEL_OCCUP1_ID, 1), \
+ CMN_EVENT_DVM(_model, _name##_dvmsync, _event, SEL_OCCUP1_ID, 2)
#define CMN_EVENT_HN_OCC(_model, _name, _type, _event) \
- _CMN_EVENT_ATTR(_model, _name##_all, _type, _event, 0, SEL_OCCUP1_ID), \
- _CMN_EVENT_ATTR(_model, _name##_read, _type, _event, 1, SEL_OCCUP1_ID), \
- _CMN_EVENT_ATTR(_model, _name##_write, _type, _event, 2, SEL_OCCUP1_ID), \
- _CMN_EVENT_ATTR(_model, _name##_atomic, _type, _event, 3, SEL_OCCUP1_ID), \
- _CMN_EVENT_ATTR(_model, _name##_stash, _type, _event, 4, SEL_OCCUP1_ID)
+ CMN_EVENT_ATTR(_model, _name##_all, _type, _event, SEL_OCCUP1_ID, 0), \
+ CMN_EVENT_ATTR(_model, _name##_read, _type, _event, SEL_OCCUP1_ID, 1), \
+ CMN_EVENT_ATTR(_model, _name##_write, _type, _event, SEL_OCCUP1_ID, 2), \
+ CMN_EVENT_ATTR(_model, _name##_atomic, _type, _event, SEL_OCCUP1_ID, 3), \
+ CMN_EVENT_ATTR(_model, _name##_stash, _type, _event, SEL_OCCUP1_ID, 4)
#define CMN_EVENT_HN_CLS(_model, _name, _type, _event) \
- _CMN_EVENT_ATTR(_model, _name##_class0, _type, _event, 0, SEL_CLASS_OCCUP_ID), \
- _CMN_EVENT_ATTR(_model, _name##_class1, _type, _event, 1, SEL_CLASS_OCCUP_ID), \
- _CMN_EVENT_ATTR(_model, _name##_class2, _type, _event, 2, SEL_CLASS_OCCUP_ID), \
- _CMN_EVENT_ATTR(_model, _name##_class3, _type, _event, 3, SEL_CLASS_OCCUP_ID)
+ CMN_EVENT_ATTR(_model, _name##_class0, _type, _event, SEL_CLASS_OCCUP_ID, 0), \
+ CMN_EVENT_ATTR(_model, _name##_class1, _type, _event, SEL_CLASS_OCCUP_ID, 1), \
+ CMN_EVENT_ATTR(_model, _name##_class2, _type, _event, SEL_CLASS_OCCUP_ID, 2), \
+ CMN_EVENT_ATTR(_model, _name##_class3, _type, _event, SEL_CLASS_OCCUP_ID, 3)
#define CMN_EVENT_HN_SNT(_model, _name, _type, _event) \
- _CMN_EVENT_ATTR(_model, _name##_all, _type, _event, 0, SEL_CBUSY_SNTHROTTLE_SEL), \
- _CMN_EVENT_ATTR(_model, _name##_group0_read, _type, _event, 1, SEL_CBUSY_SNTHROTTLE_SEL), \
- _CMN_EVENT_ATTR(_model, _name##_group0_write, _type, _event, 2, SEL_CBUSY_SNTHROTTLE_SEL), \
- _CMN_EVENT_ATTR(_model, _name##_group1_read, _type, _event, 3, SEL_CBUSY_SNTHROTTLE_SEL), \
- _CMN_EVENT_ATTR(_model, _name##_group1_write, _type, _event, 4, SEL_CBUSY_SNTHROTTLE_SEL), \
- _CMN_EVENT_ATTR(_model, _name##_read, _type, _event, 5, SEL_CBUSY_SNTHROTTLE_SEL), \
- _CMN_EVENT_ATTR(_model, _name##_write, _type, _event, 6, SEL_CBUSY_SNTHROTTLE_SEL)
+ CMN_EVENT_ATTR(_model, _name##_all, _type, _event, SEL_CBUSY_SNTHROTTLE_SEL, 0), \
+ CMN_EVENT_ATTR(_model, _name##_group0_read, _type, _event, SEL_CBUSY_SNTHROTTLE_SEL, 1), \
+ CMN_EVENT_ATTR(_model, _name##_group0_write, _type, _event, SEL_CBUSY_SNTHROTTLE_SEL, 2), \
+ CMN_EVENT_ATTR(_model, _name##_group1_read, _type, _event, SEL_CBUSY_SNTHROTTLE_SEL, 3), \
+ CMN_EVENT_ATTR(_model, _name##_group1_write, _type, _event, SEL_CBUSY_SNTHROTTLE_SEL, 4), \
+ CMN_EVENT_ATTR(_model, _name##_read, _type, _event, SEL_CBUSY_SNTHROTTLE_SEL, 5), \
+ CMN_EVENT_ATTR(_model, _name##_write, _type, _event, SEL_CBUSY_SNTHROTTLE_SEL, 6)
#define CMN_EVENT_HNF_OCC(_model, _name, _event) \
CMN_EVENT_HN_OCC(_model, hnf_##_name, CMN_TYPE_HNF, _event)
@@ -916,21 +919,21 @@ static umode_t arm_cmn_event_attr_is_visible(struct kobject *kobj,
#define CMN_EVENT_HNS_OCC(_name, _event) \
CMN_EVENT_HN_OCC(CMN_ANY, hns_##_name, CMN_TYPE_HNS, _event), \
- _CMN_EVENT_ATTR(CMN_ANY, hns_##_name##_rxsnp, CMN_TYPE_HNS, _event, 5, SEL_OCCUP1_ID), \
- _CMN_EVENT_ATTR(CMN_ANY, hns_##_name##_lbt, CMN_TYPE_HNS, _event, 6, SEL_OCCUP1_ID), \
- _CMN_EVENT_ATTR(CMN_ANY, hns_##_name##_hbt, CMN_TYPE_HNS, _event, 7, SEL_OCCUP1_ID)
+ CMN_EVENT_ATTR(CMN_ANY, hns_##_name##_rxsnp, CMN_TYPE_HNS, _event, SEL_OCCUP1_ID, 5), \
+ CMN_EVENT_ATTR(CMN_ANY, hns_##_name##_lbt, CMN_TYPE_HNS, _event, SEL_OCCUP1_ID, 6), \
+ CMN_EVENT_ATTR(CMN_ANY, hns_##_name##_hbt, CMN_TYPE_HNS, _event, SEL_OCCUP1_ID, 7)
#define CMN_EVENT_HNS_CLS( _name, _event) \
CMN_EVENT_HN_CLS(CMN_ANY, hns_##_name, CMN_TYPE_HNS, _event)
#define CMN_EVENT_HNS_SNT(_name, _event) \
CMN_EVENT_HN_SNT(CMN_ANY, hns_##_name, CMN_TYPE_HNS, _event)
#define CMN_EVENT_HNS_HBT(_name, _event) \
- _CMN_EVENT_ATTR(CMN_ANY, hns_##_name##_all, CMN_TYPE_HNS, _event, 0, SEL_HBT_LBT_SEL), \
- _CMN_EVENT_ATTR(CMN_ANY, hns_##_name##_hbt, CMN_TYPE_HNS, _event, 1, SEL_HBT_LBT_SEL), \
- _CMN_EVENT_ATTR(CMN_ANY, hns_##_name##_lbt, CMN_TYPE_HNS, _event, 2, SEL_HBT_LBT_SEL)
+ CMN_EVENT_ATTR(CMN_ANY, hns_##_name##_all, CMN_TYPE_HNS, _event, SEL_HBT_LBT_SEL, 0), \
+ CMN_EVENT_ATTR(CMN_ANY, hns_##_name##_hbt, CMN_TYPE_HNS, _event, SEL_HBT_LBT_SEL, 1), \
+ CMN_EVENT_ATTR(CMN_ANY, hns_##_name##_lbt, CMN_TYPE_HNS, _event, SEL_HBT_LBT_SEL, 2)
#define CMN_EVENT_HNS_SNH(_name, _event) \
- _CMN_EVENT_ATTR(CMN_ANY, hns_##_name##_all, CMN_TYPE_HNS, _event, 0, SEL_SN_HOME_SEL), \
- _CMN_EVENT_ATTR(CMN_ANY, hns_##_name##_sn, CMN_TYPE_HNS, _event, 1, SEL_SN_HOME_SEL), \
- _CMN_EVENT_ATTR(CMN_ANY, hns_##_name##_home, CMN_TYPE_HNS, _event, 2, SEL_SN_HOME_SEL)
+ CMN_EVENT_ATTR(CMN_ANY, hns_##_name##_all, CMN_TYPE_HNS, _event, SEL_SN_HOME_SEL, 0), \
+ CMN_EVENT_ATTR(CMN_ANY, hns_##_name##_sn, CMN_TYPE_HNS, _event, SEL_SN_HOME_SEL, 1), \
+ CMN_EVENT_ATTR(CMN_ANY, hns_##_name##_home, CMN_TYPE_HNS, _event, SEL_SN_HOME_SEL, 2)
#define _CMN_EVENT_XP_MESH(_name, _event) \
__CMN_EVENT_XP(e_##_name, (_event) | (0 << 2)), \
@@ -1814,9 +1817,9 @@ static int arm_cmn_validate_group(struct arm_cmn *cmn, struct perf_event *event)
return ret;
}
-static enum cmn_filter_select arm_cmn_filter_sel(const struct arm_cmn *cmn,
- enum cmn_node_type type,
- unsigned int eventid)
+static enum cmn_filter_select arm_cmn_event_filter(const struct arm_cmn *cmn,
+ enum cmn_node_type type,
+ unsigned int eventid)
{
struct arm_cmn_event_attr *e;
enum cmn_model model = arm_cmn_model(cmn);
@@ -1824,7 +1827,7 @@ static enum cmn_filter_select arm_cmn_filter_sel(const struct arm_cmn *cmn,
for (int i = 0; i < ARRAY_SIZE(arm_cmn_event_attrs) - 1; i++) {
e = container_of(arm_cmn_event_attrs[i], typeof(*e), attr.attr);
if (e->model & model && e->type == type && e->eventid == eventid)
- return e->fsel;
+ return e->filter[0].sel;
}
return SEL_NONE;
}
@@ -1889,7 +1892,7 @@ static int arm_cmn_event_init(struct perf_event *event)
}
/* This is sufficiently annoying to recalculate, so cache it */
- hw->filter_sel = arm_cmn_filter_sel(cmn, type, eventid);
+ hw->filter_sel = arm_cmn_event_filter(cmn, type, eventid);
bynodeid = CMN_EVENT_BYNODEID(event);
nodeid = CMN_EVENT_NODEID(event);
--
2.54.0.dirty
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v2 4/5] perf/arm-cmn: Plumb in new filter types
2026-07-28 15:59 [PATCH v2 0/5] perf/arm-cmn: Support CMN S3 r2 Robin Murphy
` (2 preceding siblings ...)
2026-07-28 15:59 ` [PATCH v2 3/5] perf/arm-cmn: Refactor event filter data Robin Murphy
@ 2026-07-28 15:59 ` Robin Murphy
2026-07-28 16:43 ` sashiko-bot
2026-07-28 15:59 ` [PATCH v2 5/5] perf/arm-cmn: Support CMN S3 r2 Robin Murphy
4 siblings, 1 reply; 9+ messages in thread
From: Robin Murphy @ 2026-07-28 15:59 UTC (permalink / raw)
To: will, mark.rutland; +Cc: linux-arm-kernel, linux-perf-users, ilkka, leo.yan
Add the logic to handle events with the upcoming new filter controls.
Since for now we will have the sole invariant of all EVICT_STATE_SEL
events having HBT_LBT_SEL as a secondary filter, for the sake of
simplicity we can just special-case that, and save the complication
of a full multi-filter abstraction until unavoidably necessary.
Reviewed-by: Leo Yan <leo.yan@arm.com>
Signed-off-by: Robin Murphy <robin.murphy@arm.com>
---
v2: Tweak commit message
drivers/perf/arm-cmn.c | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/drivers/perf/arm-cmn.c b/drivers/perf/arm-cmn.c
index 74bc2aae4a06..e25f46c38c23 100644
--- a/drivers/perf/arm-cmn.c
+++ b/drivers/perf/arm-cmn.c
@@ -166,12 +166,14 @@
#define CMN_CONFIG_FILTER GENMASK_ULL(30, 27)
#define CMN_CONFIG_BYNODEID BIT_ULL(31)
#define CMN_CONFIG_NODEID GENMASK_ULL(47, 32)
+#define CMN_CONFIG_FILTER2 GENMASK_ULL(51, 48)
#define CMN_EVENT_TYPE(event) FIELD_GET(CMN_CONFIG_TYPE, (event)->attr.config)
#define CMN_EVENT_EVENTID(event) FIELD_GET(CMN_CONFIG_EVENTID, (event)->attr.config)
#define CMN_EVENT_FILTER(event) FIELD_GET(CMN_CONFIG_FILTER, (event)->attr.config)
#define CMN_EVENT_BYNODEID(event) FIELD_GET(CMN_CONFIG_BYNODEID, (event)->attr.config)
#define CMN_EVENT_NODEID(event) FIELD_GET(CMN_CONFIG_NODEID, (event)->attr.config)
+#define CMN_EVENT_FILTER2(event) FIELD_GET(CMN_CONFIG_FILTER2, (event)->attr.config)
#define CMN_CONFIG_WP_COMBINE GENMASK_ULL(30, 27)
#define CMN_CONFIG_WP_DEV_SEL GENMASK_ULL(50, 48)
@@ -284,6 +286,9 @@ enum cmn_filter_select {
SEL_CBUSY_SNTHROTTLE_SEL,
SEL_HBT_LBT_SEL,
SEL_SN_HOME_SEL,
+ SEL_SNP_VC_SEL,
+ SEL_ENHANCED_HBT_LBT_SEL,
+ SEL_EVICT_STATE_SEL,
SEL_MAX
};
@@ -1377,6 +1382,7 @@ static struct attribute *arm_cmn_format_attrs[] = {
CMN_FORMAT_ATTR(filter, CMN_CONFIG_FILTER),
CMN_FORMAT_ATTR(bynodeid, CMN_CONFIG_BYNODEID),
CMN_FORMAT_ATTR(nodeid, CMN_CONFIG_NODEID),
+ CMN_FORMAT_ATTR(filter2, CMN_CONFIG_FILTER2),
CMN_FORMAT_ATTR(wp_dev_sel, CMN_CONFIG_WP_DEV_SEL),
CMN_FORMAT_ATTR(wp_chn_sel, CMN_CONFIG_WP_CHN_SEL),
@@ -1745,6 +1751,8 @@ static void arm_cmn_val_add_event(struct arm_cmn *cmn, struct arm_cmn_val *val,
if (sel)
val->filter[dtm][sel] = CMN_EVENT_FILTER(event) + 1;
+ if (sel == SEL_EVICT_STATE_SEL)
+ val->filter[dtm][SEL_HBT_LBT_SEL] = CMN_EVENT_FILTER2(event) + 1;
if (type != CMN_TYPE_WP)
continue;
@@ -1799,6 +1807,10 @@ static int arm_cmn_validate_group(struct arm_cmn *cmn, struct perf_event *event)
val->filter[dtm][sel] != CMN_EVENT_FILTER(event) + 1)
goto done;
+ if (sel == SEL_EVICT_STATE_SEL && val->filter[dtm][SEL_HBT_LBT_SEL] &&
+ val->filter[dtm][SEL_HBT_LBT_SEL] != CMN_EVENT_FILTER2(event) + 1)
+ goto done;
+
if (type != CMN_TYPE_WP)
continue;
@@ -1943,6 +1955,8 @@ static void arm_cmn_event_clear(struct arm_cmn *cmn, struct perf_event *event,
if (hw->filter_sel)
hw->dn[i].filter[hw->filter_sel].count--;
+ if (hw->filter_sel == SEL_EVICT_STATE_SEL)
+ hw->dn[i].filter[SEL_HBT_LBT_SEL].count--;
dtm->pmu_config_low &= ~CMN__PMEVCNT_PAIRED(dtm_idx);
writel_relaxed(dtm->pmu_config_low, dtm->base + CMN_DTM_PMU_CONFIG);
@@ -1961,6 +1975,11 @@ static int arm_cmn_set_event_filter(struct arm_cmn_node *dn, struct perf_event *
if (fsel)
ret = arm_cmn_set_event_sel_hi(dn, fsel, CMN_EVENT_FILTER(event));
+ if (fsel == SEL_EVICT_STATE_SEL && !ret) {
+ ret = arm_cmn_set_event_sel_hi(dn, SEL_HBT_LBT_SEL, CMN_EVENT_FILTER2(event));
+ if (ret)
+ dn->filter[fsel].count--;
+ }
return ret;
}
--
2.54.0.dirty
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v2 5/5] perf/arm-cmn: Support CMN S3 r2
2026-07-28 15:59 [PATCH v2 0/5] perf/arm-cmn: Support CMN S3 r2 Robin Murphy
` (3 preceding siblings ...)
2026-07-28 15:59 ` [PATCH v2 4/5] perf/arm-cmn: Plumb in new filter types Robin Murphy
@ 2026-07-28 15:59 ` Robin Murphy
2026-07-28 16:57 ` sashiko-bot
4 siblings, 1 reply; 9+ messages in thread
From: Robin Murphy @ 2026-07-28 15:59 UTC (permalink / raw)
To: will, mark.rutland; +Cc: linux-arm-kernel, linux-perf-users, ilkka, leo.yan
If you were disappointed at how minimal the initial CMN S3 support
looked compared to previous versions, then oh boy is it time to put your
party hats on... The biggest batch of incompatible changes yet comes not
with a new CMN product, but a point release of an existing one. We've
got new filters, loads of changes to existing events, register fields
moved around for no good reason, and much, much more! On the upside, we
do at least gain a means of working around the isolation feature.
As such, for the sake of sanity in the driver it is easiest to split it
into a distict "model" for our internal abstractions despite it bearing
the same part number as r0/r1.
Signed-off-by: Robin Murphy <robin.murphy@arm.com>
---
v2: No change
drivers/perf/arm-cmn.c | 270 +++++++++++++++++++++++++++++------------
1 file changed, 193 insertions(+), 77 deletions(-)
diff --git a/drivers/perf/arm-cmn.c b/drivers/perf/arm-cmn.c
index e25f46c38c23..a03e2a8ca41e 100644
--- a/drivers/perf/arm-cmn.c
+++ b/drivers/perf/arm-cmn.c
@@ -29,6 +29,7 @@
#define CMN_CI_CHILD_PTR_OFFSET GENMASK_ULL(31, 16)
#define CMN_CHILD_NODE_ADDR GENMASK(29, 0)
+#define CMN_CHILD_NODE_ISOLATED BIT(30)
#define CMN_CHILD_NODE_EXTERNAL BIT(31)
/* Some implementations use a mesh larger than the architectural max of 12 */
@@ -48,11 +49,16 @@
#define CMN_CFGM_INFO_GLOBAL 0x0900
#define CMN_INFO_MULTIPLE_DTM_EN BIT_ULL(63)
+#define CMN_S3_R2_MULTIPLE_DTM_EN BIT_ULL(59)
#define CMN_INFO_RSP_VC_NUM GENMASK_ULL(53, 52)
#define CMN_INFO_DAT_VC_NUM GENMASK_ULL(51, 50)
#define CMN_INFO_DEVICE_ISO_ENABLE BIT_ULL(44)
#define CMN_CFGM_INFO_GLOBAL_1 0x0908
+#define CMN_S3_R2_RSP_VC_NUM GENMASK_ULL(11, 9)
+#define CMN_S3_R2_DAT_VC_NUM GENMASK_ULL(8, 6)
+#define CMN_S3_R2_SNP_VC_NUM GENMASK_ULL(5, 3)
+#define CMN_S3_R2_REQ_VC_NUM GENMASK_ULL(2, 0)
#define CMN_INFO_SNP_VC_NUM GENMASK_ULL(3, 2)
#define CMN_INFO_REQ_VC_NUM GENMASK_ULL(1, 0)
@@ -78,6 +84,16 @@
/* Technically this is 4 bits wide on DNs, but we only use 2 there anyway */
#define CMN__PMU_OCCUP1_ID GENMASK_ULL(34, 32)
+/* But then... */
+#define CMN__PMU_EVICT_STATE_SEL GENMASK_ULL(54, 52)
+#define CMN__PMU_ENHANCED_HBT_LBT_SEL GENMASK_ULL(51, 48)
+#define CMN__PMU_SNP_VC_SEL GENMASK_ULL(47, 46)
+#define CMN__S3_R2_CBUSY_SNTHROTTLE_SEL GENMASK_ULL(45, 42)
+#define CMN__S3_R2_SN_HOME_SEL GENMASK_ULL(41, 40)
+#define CMN__S3_R2_HBT_LBT_SEL GENMASK_ULL(39, 38)
+#define CMN__S3_R2_CLASS_OCCUP_ID GENMASK_ULL(37, 36)
+#define CMN__S3_R2_OCCUP1_ID GENMASK_ULL(35, 32)
+
/* Some types are designed to coexist with another device in the same node */
#define CMN_CCLA_PMU_EVENT_SEL 0x008
#define CMN_HNP_PMU_EVENT_SEL 0x008
@@ -202,12 +218,14 @@ enum cmn_model {
CMN650 = 2,
CI700 = 4,
CMN700 = 8,
- CMNS3 = 16,
+ CMNS3R01 = 16,
+ CMNS3R2 = 32,
/* ...and then we can use bitmap tricks for commonality */
CMN_ANY = -1,
NOT_CMN600 = -2,
CMN_700ON = ~(CMN700 - 1),
CMN_650ON = CMN_700ON | CMN650,
+ CMNS3 = CMNS3R01 | CMNS3R2,
};
/* Actual part numbers and revision IDs defined by the hardware */
@@ -243,6 +261,10 @@ enum cmn_revision {
REV_CMNS3_R0P0 = 0,
REV_CMNS3_R0P1,
REV_CMNS3_R1P0,
+ REV_CMNS3_R2P0,
+ REV_CMNS3_R2P1,
+ REV_CMNS3_R2P2,
+ REV_CMNS3_R2P5,
REV_CI700_R0P0 = 0,
REV_CI700_R1P0,
REV_CI700_R2P0,
@@ -429,7 +451,9 @@ static enum cmn_model arm_cmn_model(const struct arm_cmn *cmn)
case PART_CI700:
return CI700;
case PART_CMN_S3:
- return CMNS3;
+ if (cmn->rev >= REV_CMNS3_R2P0)
+ return CMNS3R2;
+ return CMNS3R01;
default:
return 0;
};
@@ -609,8 +633,10 @@ enum cmn_filter_type {
FILT_OCCUP1_ID,
FILT_HNF_700,
FILT_HNS,
+ FILT_HNS_S3R2,
};
#define CMN_FILTER(_sel) [SEL_##_sel] = CMN__PMU_##_sel
+#define CMN_FILTER_V2(_sel) [SEL_##_sel] = CMN__S3_R2_##_sel
static const u64 arm_cmn_filters[][SEL_MAX] = {
[FILT_NONE] = {},
@@ -632,6 +658,17 @@ static const u64 arm_cmn_filters[][SEL_MAX] = {
CMN_FILTER(HBT_LBT_SEL),
CMN_FILTER(SN_HOME_SEL)
},
+ /* Newer HN-S */
+ [FILT_HNS_S3R2] = {
+ CMN_FILTER_V2(OCCUP1_ID),
+ CMN_FILTER_V2(CLASS_OCCUP_ID),
+ CMN_FILTER_V2(CBUSY_SNTHROTTLE_SEL),
+ CMN_FILTER_V2(HBT_LBT_SEL),
+ CMN_FILTER_V2(SN_HOME_SEL),
+ CMN_FILTER(SNP_VC_SEL),
+ CMN_FILTER(ENHANCED_HBT_LBT_SEL),
+ CMN_FILTER(EVICT_STATE_SEL)
+ }
};
static enum cmn_filter_type arm_cmn_filter(enum cmn_node_type node,
@@ -651,7 +688,9 @@ static enum cmn_filter_type arm_cmn_filter(enum cmn_node_type node,
return FILT_OCCUP1_ID;
return FILT_HNF_700;
case CMN_TYPE_HNS:
- return FILT_HNS;
+ if (model < CMNS3R2)
+ return FILT_HNS;
+ return FILT_HNS_S3R2;
};
}
@@ -732,7 +771,7 @@ struct arm_cmn_event_attr {
enum cmn_model model;
enum cmn_node_type type;
u16 eventid;
- struct arm_cmn_filter_attr filter[1];
+ struct arm_cmn_filter_attr filter[2];
};
struct arm_cmn_format_attr {
@@ -741,16 +780,16 @@ struct arm_cmn_format_attr {
int config;
};
-#define _CMN_EVENT_ATTR(_model, _name, _type, _eventid, _fa, _fb, ...) \
+#define _CMN_EVENT_ATTR(_model, _name, _type, _eventid, _fa, _fb, _fc, _fd, ...) \
(&((struct arm_cmn_event_attr[]) {{ \
.attr = __ATTR(_name, 0444, arm_cmn_event_show, NULL), \
.model = _model, \
.type = _type, \
.eventid = _eventid, \
- .filter = {{_fa, _fb}}, \
+ .filter = {{_fa, _fb}, {_fc, _fd}}, \
}})[0].attr.attr)
#define CMN_EVENT_ATTR(_model, _name, _type, _eventid, _filter...) \
- _CMN_EVENT_ATTR(_model, _name, _type, _eventid, ##_filter, 0, 0)
+ _CMN_EVENT_ATTR(_model, _name, _type, _eventid, ##_filter, 0, 0, 0, 0)
static ssize_t arm_cmn_event_show(struct device *dev,
struct device_attribute *attr, char *buf)
@@ -769,6 +808,10 @@ static ssize_t arm_cmn_event_show(struct device *dev,
"type=0x%x,eventid=0x%x,wp_dev_sel=?,wp_chn_sel=?,wp_grp=?,wp_val=?,wp_mask=?\n",
eattr->type, eattr->eventid);
+ if (filter[1].sel)
+ return sysfs_emit(buf, "type=0x%x,eventid=0x%x,filter=0x%x,filter2=0x%x\n",
+ eattr->type, eattr->eventid, filter[0].val, filter[1].val);
+
if (filter[0].sel)
return sysfs_emit(buf, "type=0x%x,eventid=0x%x,filter=0x%x\n",
eattr->type, eattr->eventid, filter[0].val);
@@ -887,8 +930,8 @@ static umode_t arm_cmn_event_attr_is_visible(struct kobject *kobj,
CMN_EVENT_ATTR(_model, ccha_##_name, CMN_TYPE_CCHA, _event)
#define CMN_EVENT_CCLA(_name, _event) \
CMN_EVENT_ATTR(CMN_ANY, ccla_##_name, CMN_TYPE_CCLA, _event)
-#define CMN_EVENT_HNS(_name, _event) \
- CMN_EVENT_ATTR(CMN_ANY, hns_##_name, CMN_TYPE_HNS, _event)
+#define _CMN_EVENT_HNS(_model, _name, _event, _filter...) \
+ CMN_EVENT_ATTR(_model, hns_##_name, CMN_TYPE_HNS, _event, ##_filter)
#define CMN_EVENT_DVM_OCC(_model, _name, _event) \
CMN_EVENT_DVM(_model, _name##_all, _event, SEL_OCCUP1_ID, 0), \
@@ -913,7 +956,12 @@ static umode_t arm_cmn_event_attr_is_visible(struct kobject *kobj,
CMN_EVENT_ATTR(_model, _name##_group1_read, _type, _event, SEL_CBUSY_SNTHROTTLE_SEL, 3), \
CMN_EVENT_ATTR(_model, _name##_group1_write, _type, _event, SEL_CBUSY_SNTHROTTLE_SEL, 4), \
CMN_EVENT_ATTR(_model, _name##_read, _type, _event, SEL_CBUSY_SNTHROTTLE_SEL, 5), \
- CMN_EVENT_ATTR(_model, _name##_write, _type, _event, SEL_CBUSY_SNTHROTTLE_SEL, 6)
+ CMN_EVENT_ATTR(_model, _name##_write, _type, _event, SEL_CBUSY_SNTHROTTLE_SEL, 6), \
+ CMN_EVENT_ATTR(CMNS3R2, _name##_ccg_read, _type, _event, SEL_CBUSY_SNTHROTTLE_SEL, 9), \
+ CMN_EVENT_ATTR(CMNS3R2, _name##_ccg_write, _type, _event, SEL_CBUSY_SNTHROTTLE_SEL, 10), \
+ CMN_EVENT_ATTR(CMNS3R2, _name##_lbt_read, _type, _event, SEL_CBUSY_SNTHROTTLE_SEL, 11), \
+ CMN_EVENT_ATTR(CMNS3R2, _name##_lbt_write, _type, _event, SEL_CBUSY_SNTHROTTLE_SEL, 12), \
+ CMN_EVENT_ATTR(CMNS3R2, _name##_lbt, _type, _event, SEL_CBUSY_SNTHROTTLE_SEL, 13)
#define CMN_EVENT_HNF_OCC(_model, _name, _event) \
CMN_EVENT_HN_OCC(_model, hnf_##_name, CMN_TYPE_HNF, _event)
@@ -922,23 +970,75 @@ static umode_t arm_cmn_event_attr_is_visible(struct kobject *kobj,
#define CMN_EVENT_HNF_SNT(_model, _name, _event) \
CMN_EVENT_HN_SNT(_model, hnf_##_name, CMN_TYPE_HNF, _event)
-#define CMN_EVENT_HNS_OCC(_name, _event) \
- CMN_EVENT_HN_OCC(CMN_ANY, hns_##_name, CMN_TYPE_HNS, _event), \
- CMN_EVENT_ATTR(CMN_ANY, hns_##_name##_rxsnp, CMN_TYPE_HNS, _event, SEL_OCCUP1_ID, 5), \
- CMN_EVENT_ATTR(CMN_ANY, hns_##_name##_lbt, CMN_TYPE_HNS, _event, SEL_OCCUP1_ID, 6), \
- CMN_EVENT_ATTR(CMN_ANY, hns_##_name##_hbt, CMN_TYPE_HNS, _event, SEL_OCCUP1_ID, 7)
+#define CMN_EVENT_HNS(_name, _event) \
+ _CMN_EVENT_HNS(CMN_ANY, _name, _event)
+#define CMN_EVENT_HNSR0(_name, _event) \
+ _CMN_EVENT_HNS(CMN700 | CMNS3R01, _name, _event)
+#define _CMN_EVENT_HNS_HBT(_model, _name, _event, _sel) \
+ _CMN_EVENT_HNS(_model, _name##_all, _event, _sel, 0), \
+ _CMN_EVENT_HNS(_model, _name##_hbt, _event, _sel, 1), \
+ _CMN_EVENT_HNS(_model, _name##_lbt, _event, _sel, 2)
+#define _CMN_EVENT_HNS_HBT2(_model, _name, _event, _fsel1, f1) \
+ _CMN_EVENT_HNS(_model, _name##_all, _event, _fsel1, f1, SEL_HBT_LBT_SEL, 0), \
+ _CMN_EVENT_HNS(_model, _name##_hbt, _event, _fsel1, f1, SEL_HBT_LBT_SEL, 1), \
+ _CMN_EVENT_HNS(_model, _name##_lbt, _event, _fsel1, f1, SEL_HBT_LBT_SEL, 2)
+
+#define CMN_EVENT_HNS_OCC(_model, _name, _event) \
+ CMN_EVENT_HN_OCC(_model, hns_##_name, CMN_TYPE_HNS, _event), \
+ _CMN_EVENT_HNS(_model, _name##_rxsnp, _event, SEL_OCCUP1_ID, 5), \
+ _CMN_EVENT_HNS(_model, _name##_lbt, _event, SEL_OCCUP1_ID, 6), \
+ _CMN_EVENT_HNS(_model, _name##_hbt, _event, SEL_OCCUP1_ID, 7), \
+ _CMN_EVENT_HNS(CMNS3R2, _name##_rnf, _event, SEL_OCCUP1_ID, 8), \
+ _CMN_EVENT_HNS(CMNS3R2, _name##_rni, _event, SEL_OCCUP1_ID, 9), \
+ _CMN_EVENT_HNS(CMNS3R2, _name##_ccglcn, _event, SEL_OCCUP1_ID, 10), \
+ _CMN_EVENT_HNS(CMNS3R2, _name##_ccgrn, _event, SEL_OCCUP1_ID, 11)
#define CMN_EVENT_HNS_CLS( _name, _event) \
CMN_EVENT_HN_CLS(CMN_ANY, hns_##_name, CMN_TYPE_HNS, _event)
-#define CMN_EVENT_HNS_SNT(_name, _event) \
- CMN_EVENT_HN_SNT(CMN_ANY, hns_##_name, CMN_TYPE_HNS, _event)
-#define CMN_EVENT_HNS_HBT(_name, _event) \
- CMN_EVENT_ATTR(CMN_ANY, hns_##_name##_all, CMN_TYPE_HNS, _event, SEL_HBT_LBT_SEL, 0), \
- CMN_EVENT_ATTR(CMN_ANY, hns_##_name##_hbt, CMN_TYPE_HNS, _event, SEL_HBT_LBT_SEL, 1), \
- CMN_EVENT_ATTR(CMN_ANY, hns_##_name##_lbt, CMN_TYPE_HNS, _event, SEL_HBT_LBT_SEL, 2)
-#define CMN_EVENT_HNS_SNH(_name, _event) \
- CMN_EVENT_ATTR(CMN_ANY, hns_##_name##_all, CMN_TYPE_HNS, _event, SEL_SN_HOME_SEL, 0), \
- CMN_EVENT_ATTR(CMN_ANY, hns_##_name##_sn, CMN_TYPE_HNS, _event, SEL_SN_HOME_SEL, 1), \
- CMN_EVENT_ATTR(CMN_ANY, hns_##_name##_home, CMN_TYPE_HNS, _event, SEL_SN_HOME_SEL, 2)
+#define CMN_EVENT_HNSR0_CLS( _name, _event) \
+ CMN_EVENT_HN_CLS(CMN700 | CMNS3R01, hns_##_name, CMN_TYPE_HNS, _event)
+#define CMN_EVENT_HNS_SNT(_model, _name, _event) \
+ CMN_EVENT_HN_SNT(_model, hns_##_name, CMN_TYPE_HNS, _event)
+#define CMN_EVENT_HNS_SNH(_model, _name, _event) \
+ _CMN_EVENT_HNS(_model, _name##_all, _event, SEL_SN_HOME_SEL, 0), \
+ _CMN_EVENT_HNS(_model, _name##_sn, _event, SEL_SN_HOME_SEL, 1), \
+ _CMN_EVENT_HNS(_model, _name##_home, _event, SEL_SN_HOME_SEL, 2)
+#define CMN_EVENT_HNS_VC(_name, _event) \
+ CMN_EVENT_HNSR0(_name, _event), \
+ _CMN_EVENT_HNS(CMNS3R2, _name##_vc0, _event, SEL_SNP_VC_SEL, 0), \
+ _CMN_EVENT_HNS(CMNS3R2, _name##_vc1, _event, SEL_SNP_VC_SEL, 1), \
+ _CMN_EVENT_HNS(CMNS3R2, _name##_vc2, _event, SEL_SNP_VC_SEL, 2)
+#define CMN_EVENT_HNS_ENHBT(_name, _event) \
+ _CMN_EVENT_HNS_HBT(CMNS3R2, _name, _event, SEL_ENHANCED_HBT_LBT_SEL), \
+ _CMN_EVENT_HNS(CMNS3R2, _name##_rnf, _event, SEL_ENHANCED_HBT_LBT_SEL, 3), \
+ _CMN_EVENT_HNS(CMNS3R2, _name##_rni, _event, SEL_ENHANCED_HBT_LBT_SEL, 4), \
+ _CMN_EVENT_HNS(CMNS3R2, _name##_ccglcn, _event, SEL_ENHANCED_HBT_LBT_SEL, 5), \
+ _CMN_EVENT_HNS(CMNS3R2, _name##_ccgrn, _event, SEL_ENHANCED_HBT_LBT_SEL, 6)
+#define CMN_EVENT_HNS_EVICT(_model, _name, _event) \
+ _CMN_EVENT_HNS_HBT2(_model, _name##_all, _event, SEL_EVICT_STATE_SEL, 0), \
+ _CMN_EVENT_HNS_HBT2(_model, _name##_eu, _event, SEL_EVICT_STATE_SEL, 1), \
+ _CMN_EVENT_HNS_HBT2(_model, _name##_en, _event, SEL_EVICT_STATE_SEL, 2), \
+ _CMN_EVENT_HNS_HBT2(_model, _name##_su, _event, SEL_EVICT_STATE_SEL, 3), \
+ _CMN_EVENT_HNS_HBT2(_model, _name##_sn, _event, SEL_EVICT_STATE_SEL, 4), \
+ _CMN_EVENT_HNS_HBT2(_model, _name##_mu, _event, SEL_EVICT_STATE_SEL, 5), \
+ _CMN_EVENT_HNS_HBT2(_model, _name##_mn, _event, SEL_EVICT_STATE_SEL, 6)
+
+#define CMN_EVENT_HNSR0_HBT(_name, _event) \
+ _CMN_EVENT_HNS_HBT(CMN700 | CMNS3R01, _name, _event, SEL_HBT_LBT_SEL)
+#define CMN_EVENT_HNS_R2SNH(_name, _event) \
+ CMN_EVENT_HNSR0(_name, _event), \
+ CMN_EVENT_HNS_SNH(CMNS3R2, _name, _event)
+#define CMN_EVENT_HNS_R2HBT(_name, _event) \
+ CMN_EVENT_HNSR0(_name, _event), \
+ _CMN_EVENT_HNS_HBT(CMNS3R2, _name, _event, SEL_HBT_LBT_SEL)
+#define CMN_EVENT_HNS_HBT_ENHBT(_name, _event) \
+ CMN_EVENT_HNSR0_HBT(_name, _event), \
+ CMN_EVENT_HNS_ENHBT(_name, _event)
+#define CMN_EVENT_HNS_HBT_OCC(_name, _event) \
+ CMN_EVENT_HNSR0_HBT(_name, _event), \
+ CMN_EVENT_HNS_OCC(CMNS3R2, _name, _event)
+#define CMN_EVENT_HNS_HBT_EVICT(_name, _event) \
+ CMN_EVENT_HNSR0_HBT(_name, _event), \
+ CMN_EVENT_HNS_EVICT(CMNS3R2, _name, _event)
#define _CMN_EVENT_XP_MESH(_name, _event) \
__CMN_EVENT_XP(e_##_name, (_event) | (0 << 2)), \
@@ -1288,65 +1388,72 @@ static struct attribute *arm_cmn_event_attrs[] = {
CMN_EVENT_CCLA(pfwd_sndr_stalls_static_crd, 0x2a),
CMN_EVENT_CCLA(pfwd_sndr_stalls_dynmaic_crd, 0x2b),
- CMN_EVENT_HNS_HBT(cache_miss, 0x01),
- CMN_EVENT_HNS_HBT(slc_sf_cache_access, 0x02),
- CMN_EVENT_HNS_HBT(cache_fill, 0x03),
- CMN_EVENT_HNS_HBT(pocq_retry, 0x04),
- CMN_EVENT_HNS_HBT(pocq_reqs_recvd, 0x05),
- CMN_EVENT_HNS_HBT(sf_hit, 0x06),
- CMN_EVENT_HNS_HBT(sf_evictions, 0x07),
- CMN_EVENT_HNS(dir_snoops_sent, 0x08),
- CMN_EVENT_HNS(brd_snoops_sent, 0x09),
- CMN_EVENT_HNS_HBT(slc_eviction, 0x0a),
- CMN_EVENT_HNS_HBT(slc_fill_invalid_way, 0x0b),
- CMN_EVENT_HNS(mc_retries_local, 0x0c),
- CMN_EVENT_HNS_SNH(mc_reqs_local, 0x0d),
+ CMN_EVENT_HNS_HBT_ENHBT(cache_miss, 0x01),
+ CMN_EVENT_HNS_HBT_ENHBT(slc_sf_cache_access, 0x02),
+ CMN_EVENT_HNS_HBT_ENHBT(cache_fill, 0x03),
+ CMN_EVENT_HNS_HBT_OCC(pocq_retry, 0x04),
+ CMN_EVENT_HNS_HBT_OCC(pocq_reqs_recvd, 0x05),
+ CMN_EVENT_HNS_HBT_ENHBT(sf_hit, 0x06),
+ CMN_EVENT_HNS_HBT_EVICT(sf_evictions, 0x07),
+ CMN_EVENT_HNS_VC(dir_snoops_sent, 0x08),
+ CMN_EVENT_HNS_VC(brd_snoops_sent, 0x09),
+ CMN_EVENT_HNS_HBT_EVICT(slc_eviction, 0x0a),
+ CMN_EVENT_HNS_HBT_ENHBT(slc_fill_invalid_way, 0x0b),
+ CMN_EVENT_HNS_R2SNH(mc_retries_local, 0x0c),
+ CMN_EVENT_HNS_SNH(CMN_ANY, mc_reqs_local, 0x0d),
CMN_EVENT_HNS(qos_hh_retry, 0x0e),
- CMN_EVENT_HNS_OCC(qos_pocq_occupancy, 0x0f),
- CMN_EVENT_HNS(pocq_addrhaz, 0x10),
- CMN_EVENT_HNS(pocq_atomic_addrhaz, 0x11),
- CMN_EVENT_HNS(ld_st_swp_adq_full, 0x12),
- CMN_EVENT_HNS(cmp_adq_full, 0x13),
+ CMN_EVENT_HNS_OCC(CMN_ANY, qos_pocq_occupancy, 0x0f),
+ CMN_EVENT_HNS_HBT_ENHBT(pocq_addrhaz, 0x10),
+ CMN_EVENT_HNS_HBT_ENHBT(pocq_atomic_addrhaz, 0x11),
+ CMN_EVENT_HNSR0(ld_st_swp_adq_full, 0x12),
+ CMN_EVENT_HNSR0(cmp_adq_full, 0x13),
CMN_EVENT_HNS(txdat_stall, 0x14),
CMN_EVENT_HNS(txrsp_stall, 0x15),
- CMN_EVENT_HNS(seq_full, 0x16),
+ CMN_EVENT_HNSR0(seq_full, 0x16),
CMN_EVENT_HNS(seq_hit, 0x17),
- CMN_EVENT_HNS(snp_sent, 0x18),
- CMN_EVENT_HNS(sfbi_dir_snp_sent, 0x19),
- CMN_EVENT_HNS(sfbi_brd_snp_sent, 0x1a),
+ CMN_EVENT_HNS_VC(snp_sent, 0x18),
+ CMN_EVENT_HNS_VC(sfbi_dir_snp_sent, 0x19),
+ CMN_EVENT_HNS_VC(sfbi_brd_snp_sent, 0x1a),
CMN_EVENT_HNS(intv_dirty, 0x1c),
- CMN_EVENT_HNS(stash_snp_sent, 0x1d),
- CMN_EVENT_HNS(stash_data_pull, 0x1e),
- CMN_EVENT_HNS(snp_fwded, 0x1f),
- CMN_EVENT_HNS(atomic_fwd, 0x20),
+ CMN_EVENT_HNSR0(stash_snp_sent, 0x1d),
+ CMN_EVENT_HNSR0(stash_data_pull, 0x1e),
+ CMN_EVENT_HNS_VC(snp_fwded, 0x1f),
+ CMN_EVENT_HNSR0(atomic_fwd, 0x20),
CMN_EVENT_HNS(mpam_hardlim, 0x21),
CMN_EVENT_HNS(mpam_softlim, 0x22),
- CMN_EVENT_HNS(snp_sent_cluster, 0x23),
- CMN_EVENT_HNS(sf_imprecise_evict, 0x24),
+ CMN_EVENT_HNS_VC(snp_sent_cluster, 0x23),
+ CMN_EVENT_HNS_R2HBT(sf_imprecise_evict, 0x24),
CMN_EVENT_HNS(sf_evict_shared_line, 0x25),
CMN_EVENT_HNS_CLS(pocq_class_occup, 0x26),
CMN_EVENT_HNS_CLS(pocq_class_retry, 0x27),
CMN_EVENT_HNS_CLS(class_mc_reqs_local, 0x28),
- CMN_EVENT_HNS_CLS(class_cgnt_cmin, 0x29),
- CMN_EVENT_HNS_SNT(sn_throttle, 0x2a),
- CMN_EVENT_HNS_SNT(sn_throttle_min, 0x2b),
+ CMN_EVENT_HNSR0_CLS(class_cgnt_cmin, 0x29),
+ CMN_EVENT_HNS_SNT(CMN_ANY, sn_throttle, 0x2a),
+ CMN_EVENT_HNS_SNT(CMN_ANY, sn_throttle_min, 0x2b),
CMN_EVENT_HNS(sf_precise_to_imprecise, 0x2c),
CMN_EVENT_HNS(snp_intv_cln, 0x2d),
CMN_EVENT_HNS(nc_excl, 0x2e),
- CMN_EVENT_HNS(excl_mon_ovfl, 0x2f),
+ CMN_EVENT_HNSR0(excl_mon_ovfl, 0x2f),
CMN_EVENT_HNS(snp_req_recvd, 0x30),
CMN_EVENT_HNS(snp_req_byp_pocq, 0x31),
CMN_EVENT_HNS(dir_ccgha_snp_sent, 0x32),
CMN_EVENT_HNS(brd_ccgha_snp_sent, 0x33),
- CMN_EVENT_HNS(ccgha_snp_stall, 0x34),
+ CMN_EVENT_HNSR0(ccgha_snp_stall, 0x34),
CMN_EVENT_HNS(lbt_req_hardlim, 0x35),
CMN_EVENT_HNS(hbt_req_hardlim, 0x36),
CMN_EVENT_HNS(sf_reupdate, 0x37),
- CMN_EVENT_HNS(excl_sf_imprecise, 0x38),
+ CMN_EVENT_HNS_R2HBT(excl_sf_imprecise, 0x38),
CMN_EVENT_HNS(snp_pocq_addrhaz, 0x39),
- CMN_EVENT_HNS(mc_retries_remote, 0x3a),
- CMN_EVENT_HNS_SNH(mc_reqs_remote, 0x3b),
+ CMN_EVENT_HNS_R2SNH(mc_retries_remote, 0x3a),
+ CMN_EVENT_HNS_SNH(CMN_ANY, mc_reqs_remote, 0x3b),
CMN_EVENT_HNS_CLS(class_mc_reqs_remote, 0x3c),
+ CMN_EVENT_HNS_ENHBT(readonce_hazard_detected, 0x3d),
+ CMN_EVENT_HNS_ENHBT(readonce_fwd_data_completed, 0x3e),
+ CMN_EVENT_HNS_SNT(CMNS3R2, cbusy00, 0x40),
+ CMN_EVENT_HNS_SNT(CMNS3R2, cbusy01, 0x41),
+ CMN_EVENT_HNS_SNT(CMNS3R2, cbusy10, 0x42),
+ CMN_EVENT_HNS_SNT(CMNS3R2, cbusy11, 0x43),
+ CMN_EVENT_HNS_ENHBT(ro_rnsd_new_alloc_hint, 0x44),
NULL
};
@@ -2431,21 +2538,33 @@ static int arm_cmn_discover(struct arm_cmn *cmn, unsigned int rgn_offset)
/*
* With the device isolation feature, if firmware has neglected to enable
* an XP port then we risk locking up if we try to access anything behind
- * it; however we also have no way to tell from Non-Secure whether any
- * given port is disabled or not, so the only way to win is not to play...
+ * it; however prior to CMN S3 r2p0 we also have no way to tell from
+ * Non-Secure whether any given port is disabled or not, so in that case
+ * the only way to win is not to play...
*/
reg = readq_relaxed(cfg_region + CMN_CFGM_INFO_GLOBAL);
- if (reg & CMN_INFO_DEVICE_ISO_ENABLE) {
+ if (reg & CMN_INFO_DEVICE_ISO_ENABLE && model == CMNS3R01) {
dev_err(cmn->dev, "Device isolation enabled, not continuing due to risk of lockup\n");
return -ENODEV;
}
- cmn->multi_dtm = reg & CMN_INFO_MULTIPLE_DTM_EN;
- cmn->rsp_vc_num = FIELD_GET(CMN_INFO_RSP_VC_NUM, reg);
- cmn->dat_vc_num = FIELD_GET(CMN_INFO_DAT_VC_NUM, reg);
+ if (model < CMNS3R2) {
+ cmn->multi_dtm = reg & CMN_INFO_MULTIPLE_DTM_EN;
+ cmn->rsp_vc_num = FIELD_GET(CMN_INFO_RSP_VC_NUM, reg);
+ cmn->dat_vc_num = FIELD_GET(CMN_INFO_DAT_VC_NUM, reg);
+ } else {
+ cmn->multi_dtm = reg & CMN_S3_R2_MULTIPLE_DTM_EN;
+ }
reg = readq_relaxed(cfg_region + CMN_CFGM_INFO_GLOBAL_1);
- cmn->snp_vc_num = FIELD_GET(CMN_INFO_SNP_VC_NUM, reg);
- cmn->req_vc_num = FIELD_GET(CMN_INFO_REQ_VC_NUM, reg);
+ if (model < CMNS3R2) {
+ cmn->snp_vc_num = FIELD_GET(CMN_INFO_SNP_VC_NUM, reg);
+ cmn->req_vc_num = FIELD_GET(CMN_INFO_REQ_VC_NUM, reg);
+ } else {
+ cmn->rsp_vc_num = FIELD_GET(CMN_S3_R2_RSP_VC_NUM, reg);
+ cmn->dat_vc_num = FIELD_GET(CMN_S3_R2_DAT_VC_NUM, reg);
+ cmn->snp_vc_num = FIELD_GET(CMN_S3_R2_SNP_VC_NUM, reg);
+ cmn->req_vc_num = FIELD_GET(CMN_S3_R2_REQ_VC_NUM, reg);
+ }
reg = readq_relaxed(cfg_region + CMN_CHILD_INFO);
child_count = FIELD_GET(CMN_CI_CHILD_COUNT, reg);
@@ -2545,15 +2664,12 @@ static int arm_cmn_discover(struct arm_cmn *cmn, unsigned int rgn_offset)
reg = readq_relaxed(xp_region + child_poff + j * 8);
/*
* Don't even try to touch anything external, since in general
- * we haven't a clue how to power up arbitrary CHI requesters.
- * As of CMN-600r1 these could only be RN-SAMs or CXLAs,
- * neither of which have any PMU events anyway.
- * (Actually, CXLAs do seem to have grown some events in r1p2,
- * but they don't go to regular XP DTMs, and they depend on
- * secure configuration which we can't easily deal with)
+ * we haven't a clue how to power up arbitrary CHI requesters,
+ * and none of them have standard PMU events anyway. Isolated
+ * nodes effectively just do not exist at all from our PoV.
*/
- if (reg & CMN_CHILD_NODE_EXTERNAL) {
- dev_dbg(cmn->dev, "ignoring external node %llx\n", reg);
+ if (reg & (CMN_CHILD_NODE_EXTERNAL | CMN_CHILD_NODE_ISOLATED)) {
+ dev_dbg(cmn->dev, "ignoring external/isolated node %llx\n", reg);
continue;
}
/*
--
2.54.0.dirty
^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v2 4/5] perf/arm-cmn: Plumb in new filter types
2026-07-28 15:59 ` [PATCH v2 4/5] perf/arm-cmn: Plumb in new filter types Robin Murphy
@ 2026-07-28 16:43 ` sashiko-bot
0 siblings, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-07-28 16:43 UTC (permalink / raw)
To: Robin Murphy; +Cc: linux-perf-users
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The newly introduced filter types (SEL_EVICT_STATE_SEL, SEL_ENHANCED_HBT_LBT_SEL, SEL_SNP_VC_SEL) are not mapped to any hardware bitmasks in `arm_cmn_filters`, causing them to be silently ignored when programming the PMU hardware.
--
--- Patch [4]: [PATCH v2 4/5] perf/arm-cmn: Plumb in new filter types ---
commit b05e0afda744759b2e246b65abc52e94120966e6
Author: Robin Murphy <robin.murphy@arm.com>
perf/arm-cmn: Plumb in new filter types
This commit adds logic to handle events with upcoming new filter controls,
special-casing the EVICT_STATE_SEL events which require HBT_LBT_SEL as a
secondary filter.
> diff --git a/drivers/perf/arm-cmn.c b/drivers/perf/arm-cmn.c
> index 6536f0ec699b7..9d5833f8cff2b 100644
> --- a/drivers/perf/arm-cmn.c
> +++ b/drivers/perf/arm-cmn.c
[ ... ]
> @@ -284,6 +286,9 @@ enum cmn_filter_select {
> SEL_CBUSY_SNTHROTTLE_SEL,
> SEL_HBT_LBT_SEL,
> SEL_SN_HOME_SEL,
> + SEL_SNP_VC_SEL,
> + SEL_ENHANCED_HBT_LBT_SEL,
> + SEL_EVICT_STATE_SEL,
> SEL_MAX
> };
[Severity: Medium]
Will these newly introduced filter types be silently ignored when programming the
PMU hardware?
It looks like they are not mapped to any hardware bitmasks in the
arm_cmn_filters translation array:
drivers/perf/arm-cmn.c:
static const u64 arm_cmn_filters[][SEL_MAX] = {
[FILT_NONE] = {},
/* DVM etc. */
Because missing entries default to 0, arm_cmn_set_event_sel_hi() appears to
bypass them when preparing the register payload:
drivers/perf/arm-cmn.c:arm_cmn_set_event_sel_hi() {
...
for (int i = SEL_OCCUP1_ID; i < SEL_MAX; i++) {
if (filter[i])
reg |= field_prep(filter[i], dn->filter[i].val);
}
...
}
Should the arm_cmn_filters array be updated to include bitmasks for these new
selectors?
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1785252273.git.robin.murphy@arm.com?part=4
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v2 5/5] perf/arm-cmn: Support CMN S3 r2
2026-07-28 15:59 ` [PATCH v2 5/5] perf/arm-cmn: Support CMN S3 r2 Robin Murphy
@ 2026-07-28 16:57 ` sashiko-bot
2026-07-28 17:16 ` Robin Murphy
0 siblings, 1 reply; 9+ messages in thread
From: sashiko-bot @ 2026-07-28 16:57 UTC (permalink / raw)
To: Robin Murphy; +Cc: linux-perf-users
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [Medium] The virtual channel count fields (rsp_vc_num, dat_vc_num, snp_vc_num, req_vc_num) in struct arm_cmn are defined as 2-bit bitfields. The patch adds support for CMN-S3 R2, which uses 3-bit values for these counts in the hardware registers. When extracting these 3-bit values and assigning them to the 2-bit bitfields, values of 4 or higher are silently truncated. Consequently, in arm_cmn_event_attr_is_visible(), the truncated value incorrectly evaluates as < 2 (e.g., 4 truncates to 0), falsely hiding valid PMU performance events from userspace.
Pre-existing issues:
- [High] In arm_cmn_discover(), the 16-bit field cmn->num_dns accumulates the number of child nodes across all XP nodes using MMIO values (CMN_CI_CHILD_COUNT). Since CMN_CI_CHILD_COUNT is a 16-bit field, accumulating multiple of these can overflow the 16-bit num_dns field. This wrapped value is used to calculate the size of the kernel heap allocation for the dn array (devm_kcalloc). During the second pass, the driver re-reads the un-wrapped hardware child counts and linearly writes to the dn array inside the loop, severely overflowing the undersized heap allocation.
--
--- Patch [5]: [PATCH v2 5/5] perf/arm-cmn: Support CMN S3 r2 ---
commit b19f5d8af33d67287a2dac3c0584d98dc6d08406
Author: Robin Murphy <robin.murphy@arm.com>
perf/arm-cmn: Support CMN S3 r2
This commit adds support for CMN S3 r2. It splits the internal model
abstraction, adds new filters, changes existing events, and provides
a means of working around the isolation feature.
> diff --git a/drivers/perf/arm-cmn.c b/drivers/perf/arm-cmn.c
> index 9d5833f8cff2b..b162de3d9d169 100644
> --- a/drivers/perf/arm-cmn.c
> +++ b/drivers/perf/arm-cmn.c
[ ... ]
> @@ -2431,21 +2538,33 @@ static int arm_cmn_discover(struct arm_cmn *cmn, unsigned int rgn_offset)
[ ... ]
> reg = readq_relaxed(cfg_region + CMN_CFGM_INFO_GLOBAL_1);
> - cmn->snp_vc_num = FIELD_GET(CMN_INFO_SNP_VC_NUM, reg);
> - cmn->req_vc_num = FIELD_GET(CMN_INFO_REQ_VC_NUM, reg);
> + if (model < CMNS3R2) {
> + cmn->snp_vc_num = FIELD_GET(CMN_INFO_SNP_VC_NUM, reg);
> + cmn->req_vc_num = FIELD_GET(CMN_INFO_REQ_VC_NUM, reg);
> + } else {
> + cmn->rsp_vc_num = FIELD_GET(CMN_S3_R2_RSP_VC_NUM, reg);
> + cmn->dat_vc_num = FIELD_GET(CMN_S3_R2_DAT_VC_NUM, reg);
> + cmn->snp_vc_num = FIELD_GET(CMN_S3_R2_SNP_VC_NUM, reg);
> + cmn->req_vc_num = FIELD_GET(CMN_S3_R2_REQ_VC_NUM, reg);
> + }
[Severity: Medium]
Does assigning the 3-bit CMN_S3_R2_RSP_VC_NUM value into the 2-bit
rsp_vc_num bitfield in struct arm_cmn cause silent truncation?
Looking at struct arm_cmn, the virtual channel fields are defined as 2 bits:
struct arm_cmn {
...
unsigned int rsp_vc_num : 2;
unsigned int dat_vc_num : 2;
unsigned int snp_vc_num : 2;
unsigned int req_vc_num : 2;
...
};
If a hardware value of 4 or 5 is truncated to 0 or 1 here, could it
incorrectly evaluate as < 2 in arm_cmn_event_attr_is_visible() and falsely
hide valid PMU events from userspace?
[ ... ]
> reg = readq_relaxed(cfg_region + CMN_CHILD_INFO);
> child_count = FIELD_GET(CMN_CI_CHILD_COUNT, reg);
>
> cmn->num_xps = child_count;
> cmn->num_dns = cmn->num_xps;
>
> /* Pass 1: visit the XPs, enumerate their children */
> cfg_region += child_poff;
> for (i = 0; i < cmn->num_xps; i++) {
> reg = readq_relaxed(cfg_region + i * 8);
> xp_region = cmn->base + (reg & CMN_CHILD_NODE_ADDR);
>
> reg = readq_relaxed(xp_region + CMN_CHILD_INFO);
> cmn->num_dns += FIELD_GET(CMN_CI_CHILD_COUNT, reg);
> }
[Severity: High]
This is a pre-existing issue, but can accumulating 16-bit hardware counts
here overflow the 16-bit cmn->num_dns field?
If multiple XP nodes return large values for CMN_CI_CHILD_COUNT, could
cmn->num_dns wrap around, resulting in an undersized devm_kcalloc()
allocation for the dn array?
Later in the second pass, the un-wrapped counts control the loop bounds,
which seems like it could lead to linear out-of-bounds writes into the
undersized array.
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1785252273.git.robin.murphy@arm.com?part=5
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v2 5/5] perf/arm-cmn: Support CMN S3 r2
2026-07-28 16:57 ` sashiko-bot
@ 2026-07-28 17:16 ` Robin Murphy
0 siblings, 0 replies; 9+ messages in thread
From: Robin Murphy @ 2026-07-28 17:16 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-perf-users
On 28/07/2026 5:57 pm, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
>
> New issues:
> - [Medium] The virtual channel count fields (rsp_vc_num, dat_vc_num, snp_vc_num, req_vc_num) in struct arm_cmn are defined as 2-bit bitfields. The patch adds support for CMN-S3 R2, which uses 3-bit values for these counts in the hardware registers. When extracting these 3-bit values and assigning them to the 2-bit bitfields, values of 4 or higher are silently truncated. Consequently, in arm_cmn_event_attr_is_visible(), the truncated value incorrectly evaluates as < 2 (e.g., 4 truncates to 0), falsely hiding valid PMU performance events from userspace.
Anyone who has a beef with the ID register field being 3 bits when the
relevant control register field is only 2 can take it up with the CMN
design team. After previous lessons learned I'm choosing to leave room
in the UAPI just in case...
> Pre-existing issues:
> - [High] In arm_cmn_discover(), the 16-bit field cmn->num_dns accumulates the number of child nodes across all XP nodes using MMIO values (CMN_CI_CHILD_COUNT). Since CMN_CI_CHILD_COUNT is a 16-bit field, accumulating multiple of these can overflow the 16-bit num_dns field. This wrapped value is used to calculate the size of the kernel heap allocation for the dn array (devm_kcalloc). During the second pass, the driver re-reads the un-wrapped hardware child counts and linearly writes to the dn array inside the loop, severely overflowing the undersized heap allocation.
But what if it was a 64-bit field and the child counts still overflowed
that? WHAT THEN? Oh noes we are doomed! Quick we must reimplement Linux
in multi-precision arithmetic forthwith!
Robin.
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-07-28 17:16 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-28 15:59 [PATCH v2 0/5] perf/arm-cmn: Support CMN S3 r2 Robin Murphy
2026-07-28 15:59 ` [PATCH v2 1/5] perf/arm-cmn: Rename filter variables for clarity Robin Murphy
2026-07-28 15:59 ` [PATCH v2 2/5] perf/arm-cmn: Refactor event filter programming Robin Murphy
2026-07-28 15:59 ` [PATCH v2 3/5] perf/arm-cmn: Refactor event filter data Robin Murphy
2026-07-28 15:59 ` [PATCH v2 4/5] perf/arm-cmn: Plumb in new filter types Robin Murphy
2026-07-28 16:43 ` sashiko-bot
2026-07-28 15:59 ` [PATCH v2 5/5] perf/arm-cmn: Support CMN S3 r2 Robin Murphy
2026-07-28 16:57 ` sashiko-bot
2026-07-28 17:16 ` Robin Murphy
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.