[PATCH v10 11/15] arm_mpam: Divide the PARTIDs between the control groups

From: Jinjie Ruan

Date: Mon Oct 19 2026 - 03:37:16 EST


From: Jinjie Ruan <ruanjinjie@xxxxxxxxxx>

To program a class that implements PARTID narrowing once per control
group, the driver needs to know which request PARTIDs belong to which
control group. Add a map from request PARTID to control group, and
program a group's configuration into every request PARTID that maps onto
it.

The map is empty until the resctrl glue code sets one up, so every
PARTID is its own control group and the behaviour is unchanged. A class
that implements narrowing writes the request-PARTID to internal-PARTID
mapping, so its configuration is only programmed for the request PARTID
that maps onto itself.

Cc: James Morse <james.morse@xxxxxxx>
Cc: Ben Horgan <ben.horgan@xxxxxxx>
Cc: Reinette Chatre <reinette.chatre@xxxxxxxxx>
Cc: Fenghua Yu <fenghuay@xxxxxxxxxx>
Signed-off-by: Zeng Heng <zengheng4@xxxxxxxxxx>
Signed-off-by: Jinjie Ruan <ruanjinjie@xxxxxxxxxx>
---
v9->v10:
- Run the T241 erratum scrub for request PARTIDs sharing an internal
PARTID
---
drivers/resctrl/mpam_devices.c | 74 +++++++++++++++++++++++++++++----
drivers/resctrl/mpam_internal.h | 2 +
2 files changed, 68 insertions(+), 8 deletions(-)

diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
index 7f88a0170c05..f81a2df11428 100644
--- a/drivers/resctrl/mpam_devices.c
+++ b/drivers/resctrl/mpam_devices.c
@@ -67,6 +67,25 @@ u8 mpam_pmg_max;
static bool partid_max_init, partid_max_published;
static DEFINE_SPINLOCK(partid_max_lock);

+/*
+ * Configuration constraints for resctrl-to-MPAM partitioning.
+ * mpam_num_closid == 0 indicates resctrl is unmounted or inactive.
+ */
+static u32 mpam_num_closid;
+static u32 mpam_num_partid;
+
+/*
+ * Map a request PARTID onto its owning control group using modulo
+ * interleaving. Without resctrl active, every PARTID maps 1:1 onto itself.
+ */
+u16 mpam_partid_to_intpartid(u16 partid)
+{
+ if (!mpam_num_closid || partid >= mpam_num_partid)
+ return partid;
+
+ return partid % mpam_num_closid;
+}
+
/*
* mpam is enabled once all devices have been probed from CPU online callbacks,
* scheduled via this work_struct. If access to an MSC depends on a CPU that
@@ -1560,14 +1579,24 @@ static void mpam_reprogram_ris_partid(struct mpam_msc_ris *ris, u16 partid,

if (mpam_has_feature(mpam_feat_partid_nrw, rprops)) {
/* Update the intpartid mapping */
+ u16 intpartid = mpam_partid_to_intpartid(partid);
+
mpam_write_partsel_reg(msc, INTPARTID,
- MPAMCFG_INTPARTID_INTERNAL | partid);
+ MPAMCFG_INTPARTID_INTERNAL | intpartid);
+
+ /*
+ * The internal PARTID holds the configuration. Only the
+ * request PARTID that maps onto itself writes the controls,
+ * the others share the same internal PARTID.
+ */
+ if (partid != intpartid)
+ goto post_config_change;

/*
* Then switch to the 'internal' partid to update the
* configuration.
*/
- __mpam_intpart_sel(ris->ris_idx, partid, msc);
+ __mpam_intpart_sel(ris->ris_idx, intpartid, msc);
}

if (mpam_has_feature(mpam_feat_cpor_part, rprops)) {
@@ -1641,6 +1670,7 @@ static void mpam_reprogram_ris_partid(struct mpam_msc_ris *ris, u16 partid,
mpam_write_partsel_reg(msc, PRI, pri_val);
}

+post_config_change:
mpam_quirk_post_config_change(ris, partid, cfg);

mutex_unlock(&msc->part_sel_lock);
@@ -1771,19 +1801,41 @@ struct mpam_write_config_arg {
static int __write_config(void *arg)
{
struct mpam_write_config_arg *c = arg;
+ u32 partid;
+
+ if (!mpam_num_closid) {
+ mpam_reprogram_ris_partid(c->ris, c->partid,
+ &c->comp->cfg[c->partid]);
+ return 0;
+ }
+
+ /* c->partid is the control group, and must be a valid one. */
+ WARN_ON_ONCE(c->partid >= mpam_num_closid);
+
+ /*
+ * A control group owns every request PARTID that maps onto it. Write
+ * the group's configuration into each of them. Resources that
+ * implement narrowing collapse them back onto a single internal
+ * PARTID, so only the first write changes the hardware.
+ */
+ for (partid = 0; partid < mpam_num_partid; partid++) {
+ if (mpam_partid_to_intpartid(partid) != c->partid)
+ continue;

- mpam_reprogram_ris_partid(c->ris, c->partid, &c->comp->cfg[c->partid]);
+ mpam_reprogram_ris_partid(c->ris, partid,
+ &c->comp->cfg[c->partid]);
+ }

return 0;
}

static void mpam_reprogram_msc(struct mpam_msc *msc)
{
- u32 partid;
- bool reset;
- struct mpam_config *cfg;
- struct mpam_msc_ris *ris;
struct mpam_write_config_arg arg;
+ struct mpam_msc_ris *ris;
+ struct mpam_config *cfg;
+ u32 partid, loop_max;
+ bool reset;

/*
* No lock for mpam_partid_max as partid_max_published has been
@@ -1803,7 +1855,13 @@ static void mpam_reprogram_msc(struct mpam_msc *msc)
arg.comp = ris->vmsc->comp;
arg.ris = ris;
reset = true;
- for (partid = 0; partid <= mpam_partid_max; partid++) {
+ /*
+ * Configurations are stored per control group. When resctrl
+ * has grouped several PARTIDs together, __write_config()
+ * expands one group into all of its PARTIDs.
+ */
+ loop_max = mpam_num_closid ? mpam_num_closid : mpam_partid_max + 1;
+ for (partid = 0; partid < loop_max; partid++) {
cfg = &ris->vmsc->comp->cfg[partid];
if (!bitmap_empty(cfg->features, MPAM_FEATURE_LAST))
reset = false;
diff --git a/drivers/resctrl/mpam_internal.h b/drivers/resctrl/mpam_internal.h
index 0a2177241615..5a7590568a05 100644
--- a/drivers/resctrl/mpam_internal.h
+++ b/drivers/resctrl/mpam_internal.h
@@ -458,6 +458,8 @@ extern struct list_head mpam_classes;
extern u16 mpam_partid_max;
extern u8 mpam_pmg_max;

+u16 mpam_partid_to_intpartid(u16 partid);
+
/* Scheduled work callback to enable mpam once all MSC have been probed */
void mpam_enable(struct work_struct *work);
void mpam_disable(struct work_struct *work);
--
2.43.0