* [PATCH RFC v2 01/19] resctrl: De-hardcode L3 monitor infrastructure
2026-08-31 17:22 [PATCH RFC v2 00/19] arm,fs/resctrl: ARM MPAM MB_NODE support Fenghua Yu
@ 2026-08-31 17:22 ` Fenghua Yu
2026-08-31 17:22 ` [PATCH RFC v2 02/19] resctrl: Expose MBA MBM counter assignment sysfs Fenghua Yu
` (18 subsequent siblings)
19 siblings, 0 replies; 21+ messages in thread
From: Fenghua Yu @ 2026-08-31 17:22 UTC (permalink / raw)
To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
Will Deacon, Catalin Marinas, Shaopeng Tan, Chen Yu, Babu Moger,
Drew Fustini, Vikram Sethi, Shanker Donthineni, Newton Liu,
Richard Cheng
Cc: linux-kernel, linux-arm-kernel, Fenghua Yu
ARM MPAM can expose MBM counters on an MBA (memory-level MSC) resource
as well as on L3, but the resctrl monitor core hardcodes
RDT_RESOURCE_L3 in domain validation, online/offline, and assignment
paths. That prevents reusing the same monitor infrastructure for MBA
without copying large chunks of code.
Generalise the monitor layer to operate on any resource ID while
keeping existing L3 userspace behaviour unchanged:
- Rename resctrl_l3_mon_resource_init/exit to resctrl_mon_init/exit
- Factor MBM counter assignment show/write helpers for reuse
- Validate monitor domains using r->rid instead of hardcoding L3
- Allow monitor domain online/offline for any resource ID
Signed-off-by: Shanker Donthineni <sdonthineni@nvidia.com>
Signed-off-by: Fenghua Yu <fenghuay@nvidia.com>
---
fs/resctrl/internal.h | 4 +-
fs/resctrl/monitor.c | 94 +++++++++++++++++++++++++++----------------
fs/resctrl/rdtgroup.c | 35 +++++++++++-----
3 files changed, 86 insertions(+), 47 deletions(-)
diff --git a/fs/resctrl/internal.h b/fs/resctrl/internal.h
index 4bd09b1541c7..af2e6e0b2af8 100644
--- a/fs/resctrl/internal.h
+++ b/fs/resctrl/internal.h
@@ -381,9 +381,9 @@ int alloc_rmid(u32 closid);
void free_rmid(u32 closid, u32 rmid);
-int resctrl_l3_mon_resource_init(void);
+int resctrl_mon_init(void);
-void resctrl_l3_mon_resource_exit(void);
+void resctrl_mon_exit(void);
void mon_event_count(void *info);
diff --git a/fs/resctrl/monitor.c b/fs/resctrl/monitor.c
index 1552ce2b4f7e..2b5654f05480 100644
--- a/fs/resctrl/monitor.c
+++ b/fs/resctrl/monitor.c
@@ -432,7 +432,7 @@ static int __l3_mon_event_count(struct rdtgroup *rdtgrp, struct rmid_read *rr)
struct mbm_state *m;
u64 tval = 0;
- if (!domain_header_is_valid(rr->hdr, RESCTRL_MON_DOMAIN, RDT_RESOURCE_L3)) {
+ if (!domain_header_is_valid(rr->hdr, RESCTRL_MON_DOMAIN, rr->r->rid)) {
rr->err = -EIO;
return -EINVAL;
}
@@ -583,7 +583,7 @@ static void mbm_bw_count(struct rdtgroup *rdtgrp, struct rmid_read *rr)
struct rdt_l3_mon_domain *d;
struct mbm_state *m;
- if (!domain_header_is_valid(rr->hdr, RESCTRL_MON_DOMAIN, RDT_RESOURCE_L3))
+ if (!domain_header_is_valid(rr->hdr, RESCTRL_MON_DOMAIN, rr->r->rid))
return;
d = container_of(rr->hdr, struct rdt_l3_mon_domain, hdr);
m = get_mbm_state(d, closid, rmid, rr->evt->evtid);
@@ -1688,9 +1688,9 @@ int resctrl_available_mbm_cntrs_show(struct kernfs_open_file *of,
return ret;
}
-int mbm_L3_assignments_show(struct kernfs_open_file *of, struct seq_file *s, void *v)
+static int mbm_assignments_show(struct kernfs_open_file *of, struct seq_file *s,
+ void *v, struct rdt_resource *r)
{
- struct rdt_resource *r = resctrl_arch_get_resource(RDT_RESOURCE_L3);
struct rdt_l3_mon_domain *d;
struct rdtgroup *rdtgrp;
struct mon_evt *mevt;
@@ -1735,6 +1735,12 @@ int mbm_L3_assignments_show(struct kernfs_open_file *of, struct seq_file *s, voi
return ret;
}
+int mbm_L3_assignments_show(struct kernfs_open_file *of, struct seq_file *s, void *v)
+{
+ return mbm_assignments_show(of, s, v,
+ resctrl_arch_get_resource(RDT_RESOURCE_L3));
+}
+
/*
* mbm_get_mon_event_by_name() - Return the mon_evt entry for the matching
* event name.
@@ -1829,10 +1835,10 @@ static int resctrl_parse_mbm_assignment(struct rdt_resource *r, struct rdtgroup
return -EINVAL;
}
-ssize_t mbm_L3_assignments_write(struct kernfs_open_file *of, char *buf,
- size_t nbytes, loff_t off)
+static ssize_t mbm_assignments_write(struct kernfs_open_file *of, char *buf,
+ size_t nbytes, loff_t off,
+ struct rdt_resource *r)
{
- struct rdt_resource *r = resctrl_arch_get_resource(RDT_RESOURCE_L3);
struct rdtgroup *rdtgrp;
char *token, *event;
int ret = 0;
@@ -1877,6 +1883,13 @@ ssize_t mbm_L3_assignments_write(struct kernfs_open_file *of, char *buf,
return ret ?: nbytes;
}
+ssize_t mbm_L3_assignments_write(struct kernfs_open_file *of, char *buf,
+ size_t nbytes, loff_t off)
+{
+ return mbm_assignments_write(of, buf, nbytes, off,
+ resctrl_arch_get_resource(RDT_RESOURCE_L3));
+}
+
static int closid_num_dirty_rmid_alloc(struct rdt_resource *r)
{
if (IS_ENABLED(CONFIG_RESCTRL_RMID_DEPENDS_ON_CLOSID)) {
@@ -1916,40 +1929,21 @@ static void closid_num_dirty_rmid_free(void)
}
}
-/**
- * resctrl_l3_mon_resource_init() - Initialise global monitoring structures.
- *
- * Allocate and initialise global monitor resources that do not belong to a
- * specific domain. i.e. the closid_num_dirty_rmid[] used to find the CLOSID
- * with the cleanest set of RMIDs.
- * Called once during boot after the struct rdt_resource's have been configured
- * but before the filesystem is mounted.
- * Resctrl's cpuhp callbacks may be called before this point to bring a domain
- * online.
- *
- * Return: 0 for success, or -ENOMEM.
- */
-int resctrl_l3_mon_resource_init(void)
+static void resctrl_mon_resource_init(struct rdt_resource *r)
{
- struct rdt_resource *r = resctrl_arch_get_resource(RDT_RESOURCE_L3);
- int ret;
+ unsigned long fflags;
- if (!r->mon_capable)
- return 0;
-
- ret = closid_num_dirty_rmid_alloc(r);
- if (ret)
- return ret;
+ fflags = RFTYPE_RES_CACHE;
if (resctrl_arch_is_evt_configurable(QOS_L3_MBM_TOTAL_EVENT_ID)) {
mon_event_all[QOS_L3_MBM_TOTAL_EVENT_ID].configurable = true;
resctrl_file_fflags_init("mbm_total_bytes_config",
- RFTYPE_MON_INFO | RFTYPE_RES_CACHE);
+ RFTYPE_MON_INFO | fflags);
}
if (resctrl_arch_is_evt_configurable(QOS_L3_MBM_LOCAL_EVENT_ID)) {
mon_event_all[QOS_L3_MBM_LOCAL_EVENT_ID].configurable = true;
resctrl_file_fflags_init("mbm_local_bytes_config",
- RFTYPE_MON_INFO | RFTYPE_RES_CACHE);
+ RFTYPE_MON_INFO | fflags);
}
if (resctrl_is_mon_event_enabled(QOS_L3_MBM_LOCAL_EVENT_ID))
@@ -1967,21 +1961,51 @@ int resctrl_l3_mon_resource_init(void)
NON_TEMP_WRITE_TO_LOCAL_MEM);
r->mon.mbm_assign_on_mkdir = true;
resctrl_file_fflags_init("num_mbm_cntrs",
- RFTYPE_MON_INFO | RFTYPE_RES_CACHE);
+ RFTYPE_MON_INFO | fflags);
resctrl_file_fflags_init("available_mbm_cntrs",
- RFTYPE_MON_INFO | RFTYPE_RES_CACHE);
+ RFTYPE_MON_INFO | fflags);
resctrl_file_fflags_init("event_filter", RFTYPE_ASSIGN_CONFIG);
if (r->mon.mbm_cntr_configurable)
resctrl_file_mode_init("event_filter", 0644);
resctrl_file_fflags_init("mbm_assign_on_mkdir", RFTYPE_MON_INFO |
- RFTYPE_RES_CACHE);
+ fflags);
resctrl_file_fflags_init("mbm_L3_assignments", RFTYPE_MON_BASE);
+ resctrl_file_fflags_init("mbm_assign_mode", RFTYPE_MON_INFO |
+ fflags);
}
+}
+
+/**
+ * resctrl_mon_init() - Initialise global monitoring structures.
+ *
+ * Allocate and initialise global monitor resources that do not belong to a
+ * specific domain. i.e. the closid_num_dirty_rmid[] used to find the CLOSID
+ * with the cleanest set of RMIDs.
+ * Called once during boot after the struct rdt_resource's have been configured
+ * but before the filesystem is mounted.
+ * Resctrl's cpuhp callbacks may be called before this point to bring a domain
+ * online.
+ *
+ * Return: 0 for success, or -ENOMEM.
+ */
+int resctrl_mon_init(void)
+{
+ struct rdt_resource *r = resctrl_arch_get_resource(RDT_RESOURCE_L3);
+ int ret;
+
+ if (!r->mon_capable)
+ return 0;
+
+ ret = closid_num_dirty_rmid_alloc(r);
+ if (ret)
+ return ret;
+
+ resctrl_mon_resource_init(r);
return 0;
}
-void resctrl_l3_mon_resource_exit(void)
+void resctrl_mon_exit(void)
{
struct rdt_resource *r = resctrl_arch_get_resource(RDT_RESOURCE_L3);
diff --git a/fs/resctrl/rdtgroup.c b/fs/resctrl/rdtgroup.c
index 416b73c57016..a658e4958562 100644
--- a/fs/resctrl/rdtgroup.c
+++ b/fs/resctrl/rdtgroup.c
@@ -5102,10 +5102,17 @@ void resctrl_offline_mon_domain(struct rdt_resource *r, struct rdt_domain_hdr *h
if (resctrl_mounted && resctrl_arch_mon_capable())
rmdir_mondata_subdir_allrdtgrp(r, hdr);
- if (r->rid != RDT_RESOURCE_L3)
+ /*
+ * The remaining teardown operates on struct rdt_l3_mon_domain, which
+ * only backs the L3 and memory bandwidth monitor resources. Other
+ * mon_capable resources (e.g. the package-scoped RDT_RESOURCE_PERF_PKG)
+ * embed a smaller domain struct, so casting their header here would read
+ * past the allocation.
+ */
+ if (r->rid != RDT_RESOURCE_L3 && r->rid != RDT_RESOURCE_MBA)
goto out_unlock;
- if (!domain_header_is_valid(hdr, RESCTRL_MON_DOMAIN, RDT_RESOURCE_L3))
+ if (!domain_header_is_valid(hdr, RESCTRL_MON_DOMAIN, r->rid))
goto out_unlock;
d = container_of(hdr, struct rdt_l3_mon_domain, hdr);
@@ -5135,7 +5142,7 @@ void resctrl_offline_mon_domain(struct rdt_resource *r, struct rdt_domain_hdr *h
* Called when the first CPU of a domain comes online, regardless of whether
* the filesystem is mounted.
* During boot this may be called before global allocations have been made by
- * resctrl_l3_mon_resource_init().
+ * resctrl_mon_init().
*
* Called during CPU online that may run as soon as CPU online callbacks
* are set up during resctrl initialization. The number of supported RMIDs
@@ -5209,10 +5216,18 @@ int resctrl_online_mon_domain(struct rdt_resource *r, struct rdt_domain_hdr *hdr
mutex_lock(&rdtgroup_mutex);
- if (r->rid != RDT_RESOURCE_L3)
+ /*
+ * domain_setup_l3_mon_state() and the workers below operate on struct
+ * rdt_l3_mon_domain, which only backs the L3 and memory bandwidth
+ * monitor resources. Other mon_capable resources (e.g. the
+ * package-scoped RDT_RESOURCE_PERF_PKG) embed a smaller domain struct,
+ * so skip the per-RMID setup for them and only create their filesystem
+ * directories.
+ */
+ if (r->rid != RDT_RESOURCE_L3 && r->rid != RDT_RESOURCE_MBA)
goto mkdir;
- if (!domain_header_is_valid(hdr, RESCTRL_MON_DOMAIN, RDT_RESOURCE_L3))
+ if (!domain_header_is_valid(hdr, RESCTRL_MON_DOMAIN, r->rid))
goto out_unlock;
d = container_of(hdr, struct rdt_l3_mon_domain, hdr);
@@ -5361,13 +5376,13 @@ int resctrl_init(void)
if (ret)
return ret;
- ret = resctrl_l3_mon_resource_init();
+ ret = resctrl_mon_init();
if (ret)
return ret;
ret = sysfs_create_mount_point(fs_kobj, "resctrl");
if (ret) {
- resctrl_l3_mon_resource_exit();
+ resctrl_mon_exit();
return ret;
}
@@ -5402,7 +5417,7 @@ int resctrl_init(void)
cleanup_mountpoint:
sysfs_remove_mount_point(fs_kobj, "resctrl");
- resctrl_l3_mon_resource_exit();
+ resctrl_mon_exit();
return ret;
}
@@ -5441,7 +5456,7 @@ static bool resctrl_online_domains_exist(void)
* When called by the architecture code, all CPUs and resctrl domains must be
* offline. This ensures the limbo and overflow handlers are not scheduled to
* run, meaning the data structures they access can be freed by
- * resctrl_l3_mon_resource_exit().
+ * resctrl_mon_exit().
*
* After resctrl_exit() returns, the architecture code should return an
* error from all resctrl_arch_ functions that can do this.
@@ -5468,6 +5483,6 @@ void resctrl_exit(void)
* it can be used to umount resctrl.
*/
- resctrl_l3_mon_resource_exit();
+ resctrl_mon_exit();
free_rmid_lru_list();
}
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [PATCH RFC v2 02/19] resctrl: Expose MBA MBM counter assignment sysfs
2026-08-31 17:22 [PATCH RFC v2 00/19] arm,fs/resctrl: ARM MPAM MB_NODE support Fenghua Yu
2026-08-31 17:22 ` [PATCH RFC v2 01/19] resctrl: De-hardcode L3 monitor infrastructure Fenghua Yu
@ 2026-08-31 17:22 ` Fenghua Yu
2026-08-31 17:22 ` [PATCH RFC v2 03/19] resctrl: name node-scoped monitor domains mon_NODE_<id> Fenghua Yu
` (17 subsequent siblings)
19 siblings, 0 replies; 21+ messages in thread
From: Fenghua Yu @ 2026-08-31 17:22 UTC (permalink / raw)
To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
Will Deacon, Catalin Marinas, Shaopeng Tan, Chen Yu, Babu Moger,
Drew Fustini, Vikram Sethi, Shanker Donthineni, Newton Liu,
Richard Cheng
Cc: linux-kernel, linux-arm-kernel, Fenghua Yu
When MBWU monitoring is backed by an MBA resource, userspace still
needs the same counter-assignment interface as on L3, but keyed to MBA
domains and exposed as mbm_NODE_assignments rather than
mbm_L3_assignments.
Add the MBA-side sysfs hooks and initialise MBA monitor metadata during
resctrl_mon_init() so mbm_* file visibility follows the resource being
configured.
Signed-off-by: Shanker Donthineni <sdonthineni@nvidia.com>
Signed-off-by: Fenghua Yu <fenghuay@nvidia.com>
---
fs/resctrl/internal.h | 5 +++++
fs/resctrl/monitor.c | 27 +++++++++++++++++++++++++--
fs/resctrl/rdtgroup.c | 11 ++++++++---
3 files changed, 38 insertions(+), 5 deletions(-)
diff --git a/fs/resctrl/internal.h b/fs/resctrl/internal.h
index af2e6e0b2af8..32c7d53790ec 100644
--- a/fs/resctrl/internal.h
+++ b/fs/resctrl/internal.h
@@ -480,6 +480,11 @@ struct resctrl_ctrl *resctrl_resource_ctrl_get_default(struct rdt_resource *r);
const char *resctrl_ctrl_name_str(enum resctrl_ctrl_name name);
bool resctrl_ctrl_is_default(struct resctrl_ctrl *ctrl);
+int mbm_NODE_assignments_show(struct kernfs_open_file *of, struct seq_file *s, void *v);
+
+ssize_t mbm_NODE_assignments_write(struct kernfs_open_file *of, char *buf,
+ size_t nbytes, loff_t off);
+
#ifdef CONFIG_RESCTRL_FS_PSEUDO_LOCK
int rdtgroup_locksetup_enter(struct rdtgroup *rdtgrp);
diff --git a/fs/resctrl/monitor.c b/fs/resctrl/monitor.c
index 2b5654f05480..5865a509174c 100644
--- a/fs/resctrl/monitor.c
+++ b/fs/resctrl/monitor.c
@@ -538,6 +538,7 @@ static int __mon_event_count(struct rdtgroup *rdtgrp, struct rmid_read *rr)
{
switch (rr->r->rid) {
case RDT_RESOURCE_L3:
+ case RDT_RESOURCE_MBA:
WARN_ON_ONCE(rr->evt->any_cpu);
if (rr->hdr)
return __l3_mon_event_count(rdtgrp, rr);
@@ -1741,6 +1742,12 @@ int mbm_L3_assignments_show(struct kernfs_open_file *of, struct seq_file *s, voi
resctrl_arch_get_resource(RDT_RESOURCE_L3));
}
+int mbm_NODE_assignments_show(struct kernfs_open_file *of, struct seq_file *s, void *v)
+{
+ return mbm_assignments_show(of, s, v,
+ resctrl_arch_get_resource(RDT_RESOURCE_MBA));
+}
+
/*
* mbm_get_mon_event_by_name() - Return the mon_evt entry for the matching
* event name.
@@ -1890,6 +1897,13 @@ ssize_t mbm_L3_assignments_write(struct kernfs_open_file *of, char *buf,
resctrl_arch_get_resource(RDT_RESOURCE_L3));
}
+ssize_t mbm_NODE_assignments_write(struct kernfs_open_file *of, char *buf,
+ size_t nbytes, loff_t off)
+{
+ return mbm_assignments_write(of, buf, nbytes, off,
+ resctrl_arch_get_resource(RDT_RESOURCE_MBA));
+}
+
static int closid_num_dirty_rmid_alloc(struct rdt_resource *r)
{
if (IS_ENABLED(CONFIG_RESCTRL_RMID_DEPENDS_ON_CLOSID)) {
@@ -1933,7 +1947,7 @@ static void resctrl_mon_resource_init(struct rdt_resource *r)
{
unsigned long fflags;
- fflags = RFTYPE_RES_CACHE;
+ fflags = (r->rid == RDT_RESOURCE_MBA) ? RFTYPE_RES_MB : RFTYPE_RES_CACHE;
if (resctrl_arch_is_evt_configurable(QOS_L3_MBM_TOTAL_EVENT_ID)) {
mon_event_all[QOS_L3_MBM_TOTAL_EVENT_ID].configurable = true;
@@ -1969,7 +1983,10 @@ static void resctrl_mon_resource_init(struct rdt_resource *r)
resctrl_file_mode_init("event_filter", 0644);
resctrl_file_fflags_init("mbm_assign_on_mkdir", RFTYPE_MON_INFO |
fflags);
- resctrl_file_fflags_init("mbm_L3_assignments", RFTYPE_MON_BASE);
+ if (r->rid == RDT_RESOURCE_MBA)
+ resctrl_file_fflags_init("mbm_NODE_assignments", RFTYPE_MON_BASE);
+ else
+ resctrl_file_fflags_init("mbm_L3_assignments", RFTYPE_MON_BASE);
resctrl_file_fflags_init("mbm_assign_mode", RFTYPE_MON_INFO |
fflags);
}
@@ -2002,6 +2019,12 @@ int resctrl_mon_init(void)
resctrl_mon_resource_init(r);
+ r = resctrl_arch_get_resource(RDT_RESOURCE_MBA);
+ if (!r->mon_capable)
+ return 0;
+
+ resctrl_mon_resource_init(r);
+
return 0;
}
diff --git a/fs/resctrl/rdtgroup.c b/fs/resctrl/rdtgroup.c
index a658e4958562..a0635888a0a0 100644
--- a/fs/resctrl/rdtgroup.c
+++ b/fs/resctrl/rdtgroup.c
@@ -2307,6 +2307,13 @@ static struct rftype res_common_files[] = {
.seq_show = mbm_L3_assignments_show,
.write = mbm_L3_assignments_write,
},
+ {
+ .name = "mbm_NODE_assignments",
+ .mode = 0644,
+ .kf_ops = &rdtgroup_kf_single_ops,
+ .seq_show = mbm_NODE_assignments_show,
+ .write = mbm_NODE_assignments_write,
+ },
{
.name = "mbm_assign_mode",
.mode = 0644,
@@ -5199,10 +5206,8 @@ int resctrl_online_ctrl_domain(struct rdt_resource *r, struct resctrl_ctrl *ctrl
mutex_lock(&rdtgroup_mutex);
- if (supports_mba_mbps(ctrl) && r->rid == RDT_RESOURCE_MBA) {
- /* RDT_RESOURCE_MBA is never mon_capable */
+ if (supports_mba_mbps(ctrl) && r->rid == RDT_RESOURCE_MBA)
err = mba_sc_domain_allocate(r, d);
- }
mutex_unlock(&rdtgroup_mutex);
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [PATCH RFC v2 03/19] resctrl: name node-scoped monitor domains mon_NODE_<id>
2026-08-31 17:22 [PATCH RFC v2 00/19] arm,fs/resctrl: ARM MPAM MB_NODE support Fenghua Yu
2026-08-31 17:22 ` [PATCH RFC v2 01/19] resctrl: De-hardcode L3 monitor infrastructure Fenghua Yu
2026-08-31 17:22 ` [PATCH RFC v2 02/19] resctrl: Expose MBA MBM counter assignment sysfs Fenghua Yu
@ 2026-08-31 17:22 ` Fenghua Yu
2026-08-31 17:22 ` [PATCH RFC v2 04/19] resctrl: Add node-scope MBM total event Fenghua Yu
` (16 subsequent siblings)
19 siblings, 0 replies; 21+ messages in thread
From: Fenghua Yu @ 2026-08-31 17:22 UTC (permalink / raw)
To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
Will Deacon, Catalin Marinas, Shaopeng Tan, Chen Yu, Babu Moger,
Drew Fustini, Vikram Sethi, Shanker Donthineni, Newton Liu,
Richard Cheng
Cc: linux-kernel, linux-arm-kernel, Fenghua Yu
Monitor domain directories under mon_data are named mon_<name>_<id>,
using the resource name as the label. For memory bandwidth monitoring on
a memory-side MSC the domain id is a NUMA node id, so labelling those
directories with the resource name ("MB") is misleading about what the
id means.
Add the RESCTRL_NODE monitor scope and a mon_domain_name() helper that
returns "NODE" for node-scoped resources (and the resource name for all
other scopes), then use it when building and removing monitor domain
directories. Node-scoped monitor domains are now named mon_NODE_<id>,
making it clear the id is a NUMA node id. L3 and telemetry naming
(mon_L3_<id>, mon_PERF_PKG_<id>) is unchanged.
Signed-off-by: Fenghua Yu <fenghuay@nvidia.com>
---
fs/resctrl/rdtgroup.c | 25 +++++++++++++++++++++----
include/linux/resctrl.h | 1 +
2 files changed, 22 insertions(+), 4 deletions(-)
diff --git a/fs/resctrl/rdtgroup.c b/fs/resctrl/rdtgroup.c
index a0635888a0a0..264666e54852 100644
--- a/fs/resctrl/rdtgroup.c
+++ b/fs/resctrl/rdtgroup.c
@@ -3967,6 +3967,20 @@ static void mon_rmdir_one_subdir(struct kernfs_node *pkn, char *name, char *subn
kernfs_remove_by_name(kn, subname);
}
+/*
+ * Label used in the mon_<label>_<id> monitor domain directory names. Node
+ * scoped monitoring (for example MB on a memory MSC) uses "NODE" so the id
+ * is understood as a NUMA node id; other scopes keep the resource name
+ * (L3 -> "L3", telemetry -> "PERF_PKG").
+ */
+static const char *mon_domain_name(struct rdt_resource *r)
+{
+ if (r->mon_scope == RESCTRL_NODE)
+ return "NODE";
+
+ return r->name;
+}
+
/*
* Remove files and directories for one SNC node. If it is the last node
* sharing an L3 cache, then remove the upper level directory containing
@@ -4010,7 +4024,7 @@ static void rmdir_mondata_subdir_allrdtgrp(struct rdt_resource *r,
return;
}
- sprintf(name, "mon_%s_%02d", r->name, hdr->id);
+ sprintf(name, "mon_%s_%02d", mon_domain_name(r), hdr->id);
list_for_each_entry(prgrp, &rdt_all_groups, rdtgroup_list) {
kernfs_remove_by_name(prgrp->mon.mon_data_kn, name);
@@ -4114,7 +4128,7 @@ static int mkdir_mondata_subdir(struct kernfs_node *parent_kn,
if (r->rid == RDT_RESOURCE_L3 && r->mon_scope == RESCTRL_L3_NODE)
return mkdir_mondata_subdir_snc(parent_kn, hdr, r, prgrp);
- sprintf(name, "mon_%s_%02d", r->name, hdr->id);
+ sprintf(name, "mon_%s_%02d", mon_domain_name(r), hdr->id);
kn = _mkdir_mondata_subdir(parent_kn, name, hdr, r, prgrp, hdr->id);
if (IS_ERR(kn))
return PTR_ERR(kn);
@@ -4169,8 +4183,11 @@ static int mkdir_mondata_subdir_alldom(struct kernfs_node *parent_kn,
* This creates a directory mon_data which contains the monitored data.
*
* mon_data has one directory for each domain which are named
- * in the format mon_<domain_name>_<domain_id>. For ex: A mon_data
- * with L3 domain looks as below:
+ * in the format mon_<domain_name>_<domain_id>. The domain name is the
+ * resource name for cache and telemetry scopes (for example "L3") and
+ * "NODE" for node scoped monitoring (for example MB on a memory MSC,
+ * where <domain_id> is a NUMA node id). For ex: A mon_data with L3 domain
+ * looks as below:
* ./mon_data:
* mon_L3_00
* mon_L3_01
diff --git a/include/linux/resctrl.h b/include/linux/resctrl.h
index 54fec07bd173..f2c3621b006e 100644
--- a/include/linux/resctrl.h
+++ b/include/linux/resctrl.h
@@ -349,6 +349,7 @@ enum resctrl_scope {
RESCTRL_L3_CACHE = 3,
RESCTRL_L3_NODE,
RESCTRL_PACKAGE,
+ RESCTRL_NODE,
};
/**
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [PATCH RFC v2 04/19] resctrl: Add node-scope MBM total event
2026-08-31 17:22 [PATCH RFC v2 00/19] arm,fs/resctrl: ARM MPAM MB_NODE support Fenghua Yu
` (2 preceding siblings ...)
2026-08-31 17:22 ` [PATCH RFC v2 03/19] resctrl: name node-scoped monitor domains mon_NODE_<id> Fenghua Yu
@ 2026-08-31 17:22 ` Fenghua Yu
2026-08-31 17:22 ` [PATCH RFC v2 05/19] resctrl: Make MBM paths resource-aware Fenghua Yu
` (15 subsequent siblings)
19 siblings, 0 replies; 21+ messages in thread
From: Fenghua Yu @ 2026-08-31 17:22 UTC (permalink / raw)
To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
Will Deacon, Catalin Marinas, Shaopeng Tan, Chen Yu, Babu Moger,
Drew Fustini, Vikram Sethi, Shanker Donthineni, Newton Liu,
Richard Cheng
Cc: linux-kernel, linux-arm-kernel, Fenghua Yu
Some MPAM systems count total memory bandwidth on a memory-side MSC that
is scoped to a NUMA node rather than the L3 cache. Add a new
QOS_NODE_MBM_TOTAL_EVENT_ID event for this node-scoped total, backed by
the MBA resource, and register it in the mon_event_all[] table.
Keep the new event id contiguous with the existing L3 MBM events so the
for_each_mbm_event_id() range and the per-domain software MBM state
arrays continue to be sized from the first to the last MBM event. Extend
resctrl_is_mbm_event() to recognise it, and add helpers
resctrl_is_mbm_total_event() and resctrl_mbm_total_event_id() so callers
can select the node-scoped total when it is enabled and fall back to the
L3 total otherwise.
Signed-off-by: Fenghua Yu <fenghuay@nvidia.com>
---
fs/resctrl/monitor.c | 2 ++
include/linux/resctrl.h | 27 +++++++++++++++++++++++----
include/linux/resctrl_types.h | 15 ++++++++++++++-
3 files changed, 39 insertions(+), 5 deletions(-)
diff --git a/fs/resctrl/monitor.c b/fs/resctrl/monitor.c
index 5865a509174c..ea9d92b6b581 100644
--- a/fs/resctrl/monitor.c
+++ b/fs/resctrl/monitor.c
@@ -1040,6 +1040,8 @@ struct mon_evt mon_event_all[QOS_NUM_EVENTS] = {
MON_EVENT(QOS_L3_OCCUP_EVENT_ID, "llc_occupancy", RDT_RESOURCE_L3, false),
MON_EVENT(QOS_L3_MBM_TOTAL_EVENT_ID, "mbm_total_bytes", RDT_RESOURCE_L3, false),
MON_EVENT(QOS_L3_MBM_LOCAL_EVENT_ID, "mbm_local_bytes", RDT_RESOURCE_L3, false),
+ MON_EVENT(QOS_NODE_MBM_TOTAL_EVENT_ID, "mbm_total_bytes",
+ RDT_RESOURCE_MBA, false),
MON_EVENT(PMT_EVENT_ENERGY, "core_energy", RDT_RESOURCE_PERF_PKG, true),
MON_EVENT(PMT_EVENT_ACTIVITY, "activity", RDT_RESOURCE_PERF_PKG, true),
MON_EVENT(PMT_EVENT_STALLS_LLC_HIT, "stalls_llc_hit", RDT_RESOURCE_PERF_PKG, false),
diff --git a/include/linux/resctrl.h b/include/linux/resctrl.h
index f2c3621b006e..195c0948b26d 100644
--- a/include/linux/resctrl.h
+++ b/include/linux/resctrl.h
@@ -633,16 +633,35 @@ bool resctrl_arch_is_evt_configurable(enum resctrl_event_id evt);
static inline bool resctrl_is_mbm_event(enum resctrl_event_id eventid)
{
- return (eventid >= QOS_L3_MBM_TOTAL_EVENT_ID &&
- eventid <= QOS_L3_MBM_LOCAL_EVENT_ID);
+ return ((eventid >= QOS_L3_MBM_TOTAL_EVENT_ID &&
+ eventid <= QOS_L3_MBM_LOCAL_EVENT_ID) ||
+ eventid == QOS_NODE_MBM_TOTAL_EVENT_ID);
+}
+
+static inline bool resctrl_is_mbm_total_event(enum resctrl_event_id eventid)
+{
+ return eventid == QOS_L3_MBM_TOTAL_EVENT_ID ||
+ eventid == QOS_NODE_MBM_TOTAL_EVENT_ID;
+}
+
+static inline enum resctrl_event_id resctrl_mbm_total_event_id(void)
+{
+ if (resctrl_is_mon_event_enabled(QOS_NODE_MBM_TOTAL_EVENT_ID))
+ return QOS_NODE_MBM_TOTAL_EVENT_ID;
+
+ return QOS_L3_MBM_TOTAL_EVENT_ID;
}
u32 resctrl_get_mon_evt_cfg(enum resctrl_event_id eventid);
-/* Iterate over all memory bandwidth events */
+/*
+ * Iterate over all memory bandwidth events. The node-scope total event is
+ * contiguous with the L3 events, so it is covered by the same range. Loop
+ * bodies must skip events that are not enabled.
+ */
#define for_each_mbm_event_id(eventid) \
for (eventid = QOS_L3_MBM_TOTAL_EVENT_ID; \
- eventid <= QOS_L3_MBM_LOCAL_EVENT_ID; eventid++)
+ eventid <= QOS_NODE_MBM_TOTAL_EVENT_ID; eventid++)
/* Iterate over memory bandwidth arrays in domain structures */
#define for_each_mbm_idx(idx) \
diff --git a/include/linux/resctrl_types.h b/include/linux/resctrl_types.h
index a5f56faa18d2..996ae8763b0a 100644
--- a/include/linux/resctrl_types.h
+++ b/include/linux/resctrl_types.h
@@ -50,6 +50,13 @@ enum resctrl_event_id {
QOS_L3_MBM_TOTAL_EVENT_ID = 0x02,
QOS_L3_MBM_LOCAL_EVENT_ID = 0x03,
+ /*
+ * Total memory bandwidth counted on a NUMA node scope MSC (e.g. a
+ * memory-side MSC above the L3). Kept contiguous with the other MPAM
+ * events so arch code can size per-event arrays by the last MPAM event.
+ */
+ QOS_NODE_MBM_TOTAL_EVENT_ID = 0x04,
+
/* Intel Telemetry Events */
PMT_EVENT_ENERGY,
PMT_EVENT_ACTIVITY,
@@ -65,7 +72,13 @@ enum resctrl_event_id {
QOS_NUM_EVENTS,
};
-#define QOS_NUM_L3_MBM_EVENTS (QOS_L3_MBM_LOCAL_EVENT_ID - QOS_L3_MBM_TOTAL_EVENT_ID + 1)
+/*
+ * Number of software MBM state slots per monitoring domain. The MBM events
+ * (L3 total, L3 local and the node-scope total) are contiguous, so the last
+ * one bounds the array. The node-scope total event is only ever enabled on
+ * Arm/MPAM; on x86 its slot is simply left unused.
+ */
+#define QOS_NUM_L3_MBM_EVENTS (QOS_NODE_MBM_TOTAL_EVENT_ID - QOS_L3_MBM_TOTAL_EVENT_ID + 1)
#define MBM_STATE_IDX(evt) ((evt) - QOS_L3_MBM_TOTAL_EVENT_ID)
#endif /* __LINUX_RESCTRL_TYPES_H */
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [PATCH RFC v2 05/19] resctrl: Make MBM paths resource-aware
2026-08-31 17:22 [PATCH RFC v2 00/19] arm,fs/resctrl: ARM MPAM MB_NODE support Fenghua Yu
` (3 preceding siblings ...)
2026-08-31 17:22 ` [PATCH RFC v2 04/19] resctrl: Add node-scope MBM total event Fenghua Yu
@ 2026-08-31 17:22 ` Fenghua Yu
2026-08-31 17:22 ` [PATCH RFC v2 06/19] arm_mpam: Support memory-level MSCs and ABMC per class Fenghua Yu
` (14 subsequent siblings)
19 siblings, 0 replies; 21+ messages in thread
From: Fenghua Yu @ 2026-08-31 17:22 UTC (permalink / raw)
To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
Will Deacon, Catalin Marinas, Shaopeng Tan, Chen Yu, Babu Moger,
Drew Fustini, Vikram Sethi, Shanker Donthineni, Newton Liu,
Richard Cheng
Cc: linux-kernel, linux-arm-kernel, Fenghua Yu
The MBM overflow, counter-assignment and event-configuration paths
hard-coded RDT_RESOURCE_L3 and the individual L3 MBM event ids. That
cannot express the node-scoped MBM total event, which is backed by the
MBA resource.
Iterate mon_event_all[] and act on every enabled MBM event whose owning
resource matches, instead of naming the L3 events explicitly. Derive the
resource of an overflow work item from the domain's rid, only run mba_sc
bandwidth feedback for the L3 resource, and initialise event
configuration for whichever MBM events a resource actually exposes. No
functional change on L3-only systems.
Signed-off-by: Fenghua Yu <fenghuay@nvidia.com>
---
fs/resctrl/ctrlmondata.c | 5 +-
fs/resctrl/monitor.c | 105 ++++++++++++++++++++++++---------------
fs/resctrl/rdtgroup.c | 64 ++++++++++++++----------
3 files changed, 109 insertions(+), 65 deletions(-)
diff --git a/fs/resctrl/ctrlmondata.c b/fs/resctrl/ctrlmondata.c
index 675531009136..4bc7090ad0c7 100644
--- a/fs/resctrl/ctrlmondata.c
+++ b/fs/resctrl/ctrlmondata.c
@@ -700,7 +700,9 @@ ssize_t rdtgroup_mba_mbps_event_write(struct kernfs_open_file *of,
else
ret = -EINVAL;
} else if (!strcmp(buf, "mbm_total_bytes")) {
- if (resctrl_is_mon_event_enabled(QOS_L3_MBM_TOTAL_EVENT_ID))
+ if (resctrl_is_mon_event_enabled(QOS_NODE_MBM_TOTAL_EVENT_ID))
+ rdtgrp->mba_mbps_event = QOS_NODE_MBM_TOTAL_EVENT_ID;
+ else if (resctrl_is_mon_event_enabled(QOS_L3_MBM_TOTAL_EVENT_ID))
rdtgrp->mba_mbps_event = QOS_L3_MBM_TOTAL_EVENT_ID;
else
ret = -EINVAL;
@@ -731,6 +733,7 @@ int rdtgroup_mba_mbps_event_show(struct kernfs_open_file *of,
seq_puts(s, "mbm_local_bytes\n");
break;
case QOS_L3_MBM_TOTAL_EVENT_ID:
+ case QOS_NODE_MBM_TOTAL_EVENT_ID:
seq_puts(s, "mbm_total_bytes\n");
break;
default:
diff --git a/fs/resctrl/monitor.c b/fs/resctrl/monitor.c
index ea9d92b6b581..d555f2d19164 100644
--- a/fs/resctrl/monitor.c
+++ b/fs/resctrl/monitor.c
@@ -809,11 +809,15 @@ static void mbm_update(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
* This is protected from concurrent reads from user as both
* the user and overflow handler hold the global mutex.
*/
- if (resctrl_is_mon_event_enabled(QOS_L3_MBM_TOTAL_EVENT_ID))
- mbm_update_one_event(r, d, rdtgrp, QOS_L3_MBM_TOTAL_EVENT_ID);
+ struct mon_evt *mevt;
- if (resctrl_is_mon_event_enabled(QOS_L3_MBM_LOCAL_EVENT_ID))
- mbm_update_one_event(r, d, rdtgrp, QOS_L3_MBM_LOCAL_EVENT_ID);
+ for_each_mon_event(mevt) {
+ if (mevt->rid != r->rid || !mevt->enabled ||
+ !resctrl_is_mbm_event(mevt->evtid))
+ continue;
+
+ mbm_update_one_event(r, d, rdtgrp, mevt->evtid);
+ }
}
/*
@@ -910,8 +914,8 @@ void mbm_handle_overflow(struct work_struct *work)
if (!is_percpu_thread())
goto out_unlock;
- r = resctrl_arch_get_resource(RDT_RESOURCE_L3);
d = container_of(work, struct rdt_l3_mon_domain, mbm_over.work);
+ r = resctrl_arch_get_resource(d->hdr.rid);
/* Domain is going offline */
if (cpumask_empty(&d->hdr.cpu_mask))
@@ -924,7 +928,7 @@ void mbm_handle_overflow(struct work_struct *work)
list_for_each_entry(crgrp, head, mon.crdtgrp_list)
mbm_update(r, d, crgrp);
- if (is_mba_sc(NULL, NULL))
+ if (is_mba_sc(NULL, NULL) && r->rid == RDT_RESOURCE_L3)
update_mba_bw(prgrp, d);
}
@@ -1321,19 +1325,20 @@ static int rdtgroup_assign_cntr_event(struct rdt_l3_mon_domain *d, struct rdtgro
*/
void rdtgroup_assign_cntrs(struct rdtgroup *rdtgrp)
{
- struct rdt_resource *r = resctrl_arch_get_resource(RDT_RESOURCE_L3);
+ struct rdt_resource *r;
+ struct mon_evt *mevt;
- if (!r->mon_capable || !resctrl_arch_mbm_cntr_assign_enabled(r) ||
- !r->mon.mbm_assign_on_mkdir)
- return;
+ for_each_mon_event(mevt) {
+ if (!mevt->enabled || !resctrl_is_mbm_event(mevt->evtid))
+ continue;
- if (resctrl_is_mon_event_enabled(QOS_L3_MBM_TOTAL_EVENT_ID))
- rdtgroup_assign_cntr_event(NULL, rdtgrp,
- &mon_event_all[QOS_L3_MBM_TOTAL_EVENT_ID]);
+ r = resctrl_arch_get_resource(mevt->rid);
+ if (!r->mon_capable || !resctrl_arch_mbm_cntr_assign_enabled(r) ||
+ !r->mon.mbm_assign_on_mkdir)
+ continue;
- if (resctrl_is_mon_event_enabled(QOS_L3_MBM_LOCAL_EVENT_ID))
- rdtgroup_assign_cntr_event(NULL, rdtgrp,
- &mon_event_all[QOS_L3_MBM_LOCAL_EVENT_ID]);
+ rdtgroup_assign_cntr_event(NULL, rdtgrp, mevt);
+ }
}
/*
@@ -1380,18 +1385,19 @@ static void rdtgroup_unassign_cntr_event(struct rdt_l3_mon_domain *d, struct rdt
*/
void rdtgroup_unassign_cntrs(struct rdtgroup *rdtgrp)
{
- struct rdt_resource *r = resctrl_arch_get_resource(RDT_RESOURCE_L3);
+ struct rdt_resource *r;
+ struct mon_evt *mevt;
- if (!r->mon_capable || !resctrl_arch_mbm_cntr_assign_enabled(r))
- return;
+ for_each_mon_event(mevt) {
+ if (!mevt->enabled || !resctrl_is_mbm_event(mevt->evtid))
+ continue;
- if (resctrl_is_mon_event_enabled(QOS_L3_MBM_TOTAL_EVENT_ID))
- rdtgroup_unassign_cntr_event(NULL, rdtgrp,
- &mon_event_all[QOS_L3_MBM_TOTAL_EVENT_ID]);
+ r = resctrl_arch_get_resource(mevt->rid);
+ if (!r->mon_capable || !resctrl_arch_mbm_cntr_assign_enabled(r))
+ continue;
- if (resctrl_is_mon_event_enabled(QOS_L3_MBM_LOCAL_EVENT_ID))
- rdtgroup_unassign_cntr_event(NULL, rdtgrp,
- &mon_event_all[QOS_L3_MBM_LOCAL_EVENT_ID]);
+ rdtgroup_unassign_cntr_event(NULL, rdtgrp, mevt);
+ }
}
static int resctrl_parse_mem_transactions(char *tok, u32 *val)
@@ -1551,6 +1557,7 @@ ssize_t resctrl_mbm_assign_mode_write(struct kernfs_open_file *of, char *buf,
{
struct rdt_resource *r = rdt_kn_parent_priv(of->kn);
struct rdt_l3_mon_domain *d;
+ struct mon_evt *mevt;
int ret = 0;
bool enable;
@@ -1602,13 +1609,19 @@ ssize_t resctrl_mbm_assign_mode_write(struct kernfs_open_file *of, char *buf,
* Initialize the default memory transaction values for
* total and local events.
*/
- if (resctrl_is_mon_event_enabled(QOS_L3_MBM_TOTAL_EVENT_ID))
- mon_event_all[QOS_L3_MBM_TOTAL_EVENT_ID].evt_cfg = r->mon.mbm_cfg_mask;
- if (resctrl_is_mon_event_enabled(QOS_L3_MBM_LOCAL_EVENT_ID))
- mon_event_all[QOS_L3_MBM_LOCAL_EVENT_ID].evt_cfg = r->mon.mbm_cfg_mask &
- (READS_TO_LOCAL_MEM |
- READS_TO_LOCAL_S_MEM |
- NON_TEMP_WRITE_TO_LOCAL_MEM);
+ for_each_mon_event(mevt) {
+ if (mevt->rid != r->rid || !mevt->enabled ||
+ !resctrl_is_mbm_event(mevt->evtid))
+ continue;
+
+ if (mevt->evtid == QOS_L3_MBM_LOCAL_EVENT_ID)
+ mevt->evt_cfg = r->mon.mbm_cfg_mask &
+ (READS_TO_LOCAL_MEM |
+ READS_TO_LOCAL_S_MEM |
+ NON_TEMP_WRITE_TO_LOCAL_MEM);
+ else
+ mevt->evt_cfg = r->mon.mbm_cfg_mask;
+ }
/* Enable auto assignment when switching to "mbm_event" mode */
if (enable)
r->mon.mbm_assign_on_mkdir = true;
@@ -1947,6 +1960,7 @@ static void closid_num_dirty_rmid_free(void)
static void resctrl_mon_resource_init(struct rdt_resource *r)
{
+ struct mon_evt *mevt;
unsigned long fflags;
fflags = (r->rid == RDT_RESOURCE_MBA) ? RFTYPE_RES_MB : RFTYPE_RES_CACHE;
@@ -1956,6 +1970,11 @@ static void resctrl_mon_resource_init(struct rdt_resource *r)
resctrl_file_fflags_init("mbm_total_bytes_config",
RFTYPE_MON_INFO | fflags);
}
+ if (resctrl_arch_is_evt_configurable(QOS_NODE_MBM_TOTAL_EVENT_ID)) {
+ mon_event_all[QOS_NODE_MBM_TOTAL_EVENT_ID].configurable = true;
+ resctrl_file_fflags_init("mbm_total_bytes_config",
+ RFTYPE_MON_INFO | fflags);
+ }
if (resctrl_arch_is_evt_configurable(QOS_L3_MBM_LOCAL_EVENT_ID)) {
mon_event_all[QOS_L3_MBM_LOCAL_EVENT_ID].configurable = true;
resctrl_file_fflags_init("mbm_local_bytes_config",
@@ -1964,17 +1983,25 @@ static void resctrl_mon_resource_init(struct rdt_resource *r)
if (resctrl_is_mon_event_enabled(QOS_L3_MBM_LOCAL_EVENT_ID))
mba_mbps_default_event = QOS_L3_MBM_LOCAL_EVENT_ID;
+ else if (resctrl_is_mon_event_enabled(QOS_NODE_MBM_TOTAL_EVENT_ID))
+ mba_mbps_default_event = QOS_NODE_MBM_TOTAL_EVENT_ID;
else if (resctrl_is_mon_event_enabled(QOS_L3_MBM_TOTAL_EVENT_ID))
mba_mbps_default_event = QOS_L3_MBM_TOTAL_EVENT_ID;
if (r->mon.mbm_cntr_assignable) {
- if (resctrl_is_mon_event_enabled(QOS_L3_MBM_TOTAL_EVENT_ID))
- mon_event_all[QOS_L3_MBM_TOTAL_EVENT_ID].evt_cfg = r->mon.mbm_cfg_mask;
- if (resctrl_is_mon_event_enabled(QOS_L3_MBM_LOCAL_EVENT_ID))
- mon_event_all[QOS_L3_MBM_LOCAL_EVENT_ID].evt_cfg = r->mon.mbm_cfg_mask &
- (READS_TO_LOCAL_MEM |
- READS_TO_LOCAL_S_MEM |
- NON_TEMP_WRITE_TO_LOCAL_MEM);
+ for_each_mon_event(mevt) {
+ if (mevt->rid != r->rid || !mevt->enabled ||
+ !resctrl_is_mbm_event(mevt->evtid))
+ continue;
+
+ if (mevt->evtid == QOS_L3_MBM_LOCAL_EVENT_ID)
+ mevt->evt_cfg = r->mon.mbm_cfg_mask &
+ (READS_TO_LOCAL_MEM |
+ READS_TO_LOCAL_S_MEM |
+ NON_TEMP_WRITE_TO_LOCAL_MEM);
+ else
+ mevt->evt_cfg = r->mon.mbm_cfg_mask;
+ }
r->mon.mbm_assign_on_mkdir = true;
resctrl_file_fflags_init("num_mbm_cntrs",
RFTYPE_MON_INFO | fflags);
diff --git a/fs/resctrl/rdtgroup.c b/fs/resctrl/rdtgroup.c
index 264666e54852..d60868f7d5e7 100644
--- a/fs/resctrl/rdtgroup.c
+++ b/fs/resctrl/rdtgroup.c
@@ -134,7 +134,8 @@ void rdt_staged_configs_clear(void)
static bool resctrl_is_mbm_enabled(void)
{
return (resctrl_is_mon_event_enabled(QOS_L3_MBM_TOTAL_EVENT_ID) ||
- resctrl_is_mon_event_enabled(QOS_L3_MBM_LOCAL_EVENT_ID));
+ resctrl_is_mon_event_enabled(QOS_L3_MBM_LOCAL_EVENT_ID) ||
+ resctrl_is_mon_event_enabled(QOS_NODE_MBM_TOTAL_EVENT_ID));
}
/*
@@ -1929,7 +1930,7 @@ static int mbm_total_bytes_config_show(struct kernfs_open_file *of,
if (!info_kn_lock(of->kn))
return -ENOENT;
- mbm_config_show(seq, r, QOS_L3_MBM_TOTAL_EVENT_ID);
+ mbm_config_show(seq, r, resctrl_mbm_total_event_id());
info_kn_unlock(of->kn);
return 0;
@@ -2053,7 +2054,7 @@ static ssize_t mbm_total_bytes_config_write(struct kernfs_open_file *of,
buf[nbytes - 1] = '\0';
- ret = mon_config_write(r, buf, QOS_L3_MBM_TOTAL_EVENT_ID);
+ ret = mon_config_write(r, buf, resctrl_mbm_total_event_id());
out_unlock:
info_kn_unlock(of->kn);
@@ -5317,10 +5318,39 @@ static struct rdt_l3_mon_domain *get_mon_domain_from_cpu(int cpu,
return NULL;
}
-void resctrl_offline_cpu(unsigned int cpu)
+/* rdtgroup_mutex must be held when this helper is called. */
+static void resctrl_migrate_mon_domain_workers(unsigned int cpu,
+ struct rdt_resource *r)
{
- struct rdt_resource *l3 = resctrl_arch_get_resource(RDT_RESOURCE_L3);
struct rdt_l3_mon_domain *d;
+
+ lockdep_assert_held(&rdtgroup_mutex);
+
+ if (!r->mon_capable)
+ return;
+
+ d = get_mon_domain_from_cpu(cpu, r);
+ if (!d)
+ return;
+
+ if (resctrl_is_mbm_enabled() && cpu == d->mbm_work_cpu) {
+ mutex_unlock(&rdtgroup_mutex);
+ cancel_delayed_work_sync(&d->mbm_over);
+ mutex_lock(&rdtgroup_mutex);
+ mbm_setup_overflow_handler(d, 0, cpu);
+ }
+ if (r->rid == RDT_RESOURCE_L3 &&
+ resctrl_is_mon_event_enabled(QOS_L3_OCCUP_EVENT_ID) &&
+ cpu == d->cqm_work_cpu && has_busy_rmid(d)) {
+ mutex_unlock(&rdtgroup_mutex);
+ cancel_delayed_work_sync(&d->cqm_limbo);
+ mutex_lock(&rdtgroup_mutex);
+ cqm_setup_limbo_handler(d, 0, cpu);
+ }
+}
+
+void resctrl_offline_cpu(unsigned int cpu)
+{
struct rdtgroup *rdtgrp;
mutex_lock(&rdtgroup_mutex);
@@ -5331,27 +5361,11 @@ void resctrl_offline_cpu(unsigned int cpu)
}
}
- if (!l3->mon_capable)
- goto out_unlock;
+ resctrl_migrate_mon_domain_workers(cpu,
+ resctrl_arch_get_resource(RDT_RESOURCE_L3));
+ resctrl_migrate_mon_domain_workers(cpu,
+ resctrl_arch_get_resource(RDT_RESOURCE_MBA));
- d = get_mon_domain_from_cpu(cpu, l3);
- if (d) {
- if (resctrl_is_mbm_enabled() && cpu == d->mbm_work_cpu) {
- mutex_unlock(&rdtgroup_mutex);
- cancel_delayed_work_sync(&d->mbm_over);
- mutex_lock(&rdtgroup_mutex);
- mbm_setup_overflow_handler(d, 0, cpu);
- }
- if (resctrl_is_mon_event_enabled(QOS_L3_OCCUP_EVENT_ID) &&
- cpu == d->cqm_work_cpu && has_busy_rmid(d)) {
- mutex_unlock(&rdtgroup_mutex);
- cancel_delayed_work_sync(&d->cqm_limbo);
- mutex_lock(&rdtgroup_mutex);
- cqm_setup_limbo_handler(d, 0, cpu);
- }
- }
-
-out_unlock:
mutex_unlock(&rdtgroup_mutex);
}
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [PATCH RFC v2 06/19] arm_mpam: Support memory-level MSCs and ABMC per class
2026-08-31 17:22 [PATCH RFC v2 00/19] arm,fs/resctrl: ARM MPAM MB_NODE support Fenghua Yu
` (4 preceding siblings ...)
2026-08-31 17:22 ` [PATCH RFC v2 05/19] resctrl: Make MBM paths resource-aware Fenghua Yu
@ 2026-08-31 17:22 ` Fenghua Yu
2026-08-31 17:22 ` [PATCH RFC v2 07/19] arm_mpam: Refine L3 topology and class selection Fenghua Yu
` (13 subsequent siblings)
19 siblings, 0 replies; 21+ messages in thread
From: Fenghua Yu @ 2026-08-31 17:22 UTC (permalink / raw)
To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
Will Deacon, Catalin Marinas, Shaopeng Tan, Chen Yu, Babu Moger,
Drew Fustini, Vikram Sethi, Shanker Donthineni, Newton Liu,
Richard Cheng
Cc: linux-kernel, linux-arm-kernel, Fenghua Yu
Some platforms place MBWU counters on a memory MSC above L3 while CSU
occupancy may remain on the L3 cache MSC. The driver previously assumed
all monitors and ABMC state lived on the L3 resource, which
mis-associates MBA counters, breaks mbm_L3_assignments when multiple
events share a class, and uses the wrong domain ID space for
memory-level MSCs.
Teach MPAM resctrl to map monitor classes to L3 or MBA resources,
initialise ABMC on the resource that owns the MBWU monitor, select the
node-scope MBM total event for memory-side counters, and test
mbm_cntr_assignable instead of inferring assignment support from
whichever monitor happens to share res->class first.
Signed-off-by: Shanker Donthineni <sdonthineni@nvidia.com>
Signed-off-by: Fenghua Yu <fenghuay@nvidia.com>
---
drivers/resctrl/mpam_resctrl.c | 112 ++++++++++++++++++++++++---------
1 file changed, 81 insertions(+), 31 deletions(-)
diff --git a/drivers/resctrl/mpam_resctrl.c b/drivers/resctrl/mpam_resctrl.c
index ca2ae4b5cc8a..47a7b18d841d 100644
--- a/drivers/resctrl/mpam_resctrl.c
+++ b/drivers/resctrl/mpam_resctrl.c
@@ -45,7 +45,7 @@ static struct mpam_resctrl_res mpam_resctrl_controls[RDT_NUM_RESOURCES];
* to those supported by MPAM.
* Class pointer may be NULL.
*/
-#define MPAM_MAX_EVENT QOS_L3_MBM_TOTAL_EVENT_ID
+#define MPAM_MAX_EVENT QOS_NODE_MBM_TOTAL_EVENT_ID
static struct mpam_resctrl_mon mpam_resctrl_counters[MPAM_MAX_EVENT + 1];
#define for_each_mpam_resctrl_mon(mon, eventid) \
@@ -88,6 +88,11 @@ bool resctrl_arch_alloc_capable(void)
return false;
}
+static bool mpam_class_memory(struct mpam_class *class)
+{
+ return class && class->type == MPAM_CLASS_MEMORY && class->level > 3;
+}
+
bool resctrl_arch_mon_capable(void)
{
struct mpam_resctrl_res *res = &mpam_resctrl_controls[RDT_RESOURCE_L3];
@@ -121,7 +126,13 @@ void resctrl_arch_reset_rmid(struct rdt_resource *r, struct rdt_l3_mon_domain *d
bool resctrl_arch_mbm_cntr_assign_enabled(struct rdt_resource *r)
{
- return (r == &mpam_resctrl_controls[RDT_RESOURCE_L3].resctrl_res);
+ /*
+ * mbm_cntr_assignable is set only when ABMC is initialised on this
+ * resource. Multiple monitor events may share res->class (e.g.
+ * occupancy and MBWU), so assignment support must not be inferred
+ * from mon->assigned_counters via a class lookup on the wrong event.
+ */
+ return r->mon.mbm_cntr_assignable;
}
int resctrl_arch_mbm_cntr_assign_set(struct rdt_resource *r, bool enable)
@@ -181,24 +192,41 @@ static void resctrl_reset_task_closids(void)
read_unlock(&tasklist_lock);
}
-static void mpam_resctrl_monitor_sync_abmc_vals(struct rdt_resource *l3)
+static struct
+mpam_resctrl_res *mpam_resctrl_res_from_mon(struct mpam_resctrl_mon *mon)
{
- struct mpam_resctrl_mon *mon = &mpam_resctrl_counters[QOS_L3_MBM_TOTAL_EVENT_ID];
+ struct mpam_resctrl_res *res;
+ enum resctrl_res_level rid;
if (!mon->class)
- return;
+ return NULL;
+
+ for_each_mpam_resctrl_control(res, rid) {
+ if (res->class == mon->class)
+ return res;
+ }
+
+ return NULL;
+}
+
+static struct mpam_resctrl_mon *mpam_resctrl_mbm_total_mon(void)
+{
+ if (mpam_resctrl_counters[QOS_NODE_MBM_TOTAL_EVENT_ID].class)
+ return &mpam_resctrl_counters[QOS_NODE_MBM_TOTAL_EVENT_ID];
+
+ return &mpam_resctrl_counters[QOS_L3_MBM_TOTAL_EVENT_ID];
+}
+
+static void mpam_resctrl_monitor_sync_abmc_vals(struct rdt_resource *r)
+{
+ struct mpam_resctrl_mon *mon = mpam_resctrl_mbm_total_mon();
- if (!mon->assigned_counters)
+ if (!mon->class || !mon->assigned_counters)
return;
- l3->mon.num_mbm_cntrs = mon->class->props.num_mbwu_mon;
+ r->mon.num_mbm_cntrs = mon->class->props.num_mbwu_mon;
if (cdp_enabled)
- l3->mon.num_mbm_cntrs /= 2;
-
- /*
- * Continue as normal even if enabling cdp causes there to be
- * zero counters. This avoids giving resctrl mixed messages.
- */
+ r->mon.num_mbm_cntrs /= 2;
}
int resctrl_arch_set_cdp_enabled(struct rdt_resource *r, struct resctrl_ctrl *ctrl,
@@ -404,6 +432,7 @@ static int resctrl_arch_mon_ctx_alloc_no_wait(enum resctrl_event_id evtid)
return mpam_alloc_csu_mon(mon->class);
case QOS_L3_MBM_LOCAL_EVENT_ID:
case QOS_L3_MBM_TOTAL_EVENT_ID:
+ case QOS_NODE_MBM_TOTAL_EVENT_ID:
return USE_PRE_ALLOCATED;
default:
return -EOPNOTSUPP;
@@ -1160,7 +1189,10 @@ static void mpam_resctrl_pick_counters(void)
* and it's equivalent to mbm_total and so always use
* mbm_total.
*/
- counter_update_class(QOS_L3_MBM_TOTAL_EVENT_ID, class);
+ if (class->type == MPAM_CLASS_MEMORY)
+ counter_update_class(QOS_NODE_MBM_TOTAL_EVENT_ID, class);
+ else
+ counter_update_class(QOS_L3_MBM_TOTAL_EVENT_ID, class);
}
}
}
@@ -1187,7 +1219,7 @@ void resctrl_arch_config_cntr(struct rdt_resource *r, struct rdt_l3_mon_domain *
{
struct mpam_resctrl_mon *mon = &mpam_resctrl_counters[evtid];
- if (evtid != QOS_L3_MBM_TOTAL_EVENT_ID) {
+ if (!resctrl_is_mbm_total_event(evtid)) {
pr_debug("unexpected event id\n");
return;
}
@@ -1204,7 +1236,7 @@ void resctrl_arch_config_cntr(struct rdt_resource *r, struct rdt_l3_mon_domain *
__config_cntr(mon, cntr_id, CDP_NONE, closid, rmid, assign);
}
- resctrl_arch_reset_cntr(r, d, closid, rmid, cntr_id, QOS_L3_MBM_TOTAL_EVENT_ID);
+ resctrl_arch_reset_cntr(r, d, closid, rmid, cntr_id, evtid);
}
static int mpam_resctrl_control_init(struct mpam_resctrl_res *res)
@@ -1303,10 +1335,10 @@ static int mpam_resctrl_pick_domain_id(int cpu, struct mpam_component *comp)
*/
static int mpam_resctrl_monitor_init_abmc(struct mpam_resctrl_mon *mon)
{
- struct mpam_resctrl_res *res = &mpam_resctrl_controls[RDT_RESOURCE_L3];
size_t num_rmid = resctrl_arch_system_num_rmid_idx();
- struct rdt_resource *l3 = &res->resctrl_res;
struct mpam_class *class = mon->class;
+ struct mpam_resctrl_res *res;
+ struct rdt_resource *r;
u16 num_mbwu_mon;
int *cntrs;
@@ -1317,6 +1349,12 @@ static int mpam_resctrl_monitor_init_abmc(struct mpam_resctrl_mon *mon)
}
memset(rmid_array, -1, num_rmid * sizeof(*rmid_array));
+ res = mpam_resctrl_res_from_mon(mon);
+ if (!res)
+ return -EINVAL;
+
+ r = &res->resctrl_res;
+
num_mbwu_mon = class->props.num_mbwu_mon;
cntrs = __alloc_mbwu_array(mon->class, num_mbwu_mon);
if (IS_ERR(cntrs))
@@ -1324,12 +1362,12 @@ static int mpam_resctrl_monitor_init_abmc(struct mpam_resctrl_mon *mon)
mon->assigned_counters = cntrs;
mon->mbwu_idx_to_mon = no_free_ptr(rmid_array);
- l3->mon.mbm_cntr_assignable = true;
- l3->mon.mbm_assign_on_mkdir = true;
- l3->mon.mbm_cntr_configurable = false;
- l3->mon.mbm_cntr_assign_fixed = true;
+ r->mon.mbm_cntr_assignable = true;
+ r->mon.mbm_assign_on_mkdir = true;
+ r->mon.mbm_cntr_configurable = false;
+ r->mon.mbm_cntr_assign_fixed = true;
- mpam_resctrl_monitor_sync_abmc_vals(l3);
+ mpam_resctrl_monitor_sync_abmc_vals(r);
return 0;
}
@@ -1337,8 +1375,15 @@ static int mpam_resctrl_monitor_init_abmc(struct mpam_resctrl_mon *mon)
static int mpam_resctrl_monitor_init(struct mpam_resctrl_mon *mon,
enum resctrl_event_id type)
{
- struct mpam_resctrl_res *res = &mpam_resctrl_controls[RDT_RESOURCE_L3];
- struct rdt_resource *l3 = &res->resctrl_res;
+ struct mpam_resctrl_res *res;
+ struct rdt_resource *r;
+
+ if (mpam_class_memory(mon->class))
+ res = &mpam_resctrl_controls[RDT_RESOURCE_MBA];
+ else
+ res = &mpam_resctrl_controls[RDT_RESOURCE_L3];
+
+ r = &res->resctrl_res;
lockdep_assert_cpus_held();
@@ -1365,8 +1410,13 @@ static int mpam_resctrl_monitor_init(struct mpam_resctrl_mon *mon,
* monitoring class.
* Setting name is necessary on monitor only platforms.
*/
- l3->name = "L3";
- l3->mon_scope = RESCTRL_L3_CACHE;
+ if (mpam_class_memory(mon->class)) {
+ r->name = "MB";
+ r->mon_scope = RESCTRL_NODE;
+ } else {
+ r->name = "L3";
+ r->mon_scope = RESCTRL_L3_CACHE;
+ }
/*
* num-rmid is the upper bound for the number of monitoring groups that
@@ -1376,9 +1426,9 @@ static int mpam_resctrl_monitor_init(struct mpam_resctrl_mon *mon,
* this does mean userspace needs to know the architecture to correctly
* interpret this value.
*/
- l3->mon.num_rmid = resctrl_arch_system_num_rmid_idx();
+ r->mon.num_rmid = resctrl_arch_system_num_rmid_idx();
- if (type == QOS_L3_MBM_TOTAL_EVENT_ID) {
+ if (type == QOS_L3_MBM_TOTAL_EVENT_ID || type == QOS_NODE_MBM_TOTAL_EVENT_ID) {
int err;
err = mpam_resctrl_monitor_init_abmc(mon);
@@ -1386,13 +1436,13 @@ static int mpam_resctrl_monitor_init(struct mpam_resctrl_mon *mon,
return err;
static_assert(MAX_EVT_CONFIG_BITS == 0x7f);
- l3->mon.mbm_cfg_mask = MAX_EVT_CONFIG_BITS;
+ r->mon.mbm_cfg_mask = MAX_EVT_CONFIG_BITS;
}
if (!resctrl_enable_mon_event(type, false, 0, NULL))
return -EINVAL;
- l3->mon_capable = true;
+ r->mon_capable = true;
return 0;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [PATCH RFC v2 07/19] arm_mpam: Refine L3 topology and class selection
2026-08-31 17:22 [PATCH RFC v2 00/19] arm,fs/resctrl: ARM MPAM MB_NODE support Fenghua Yu
` (5 preceding siblings ...)
2026-08-31 17:22 ` [PATCH RFC v2 06/19] arm_mpam: Support memory-level MSCs and ABMC per class Fenghua Yu
@ 2026-08-31 17:22 ` Fenghua Yu
2026-08-31 17:22 ` [PATCH RFC v2 08/19] arm_mpam: Include all MSC components during domain setup Fenghua Yu
` (12 subsequent siblings)
19 siblings, 0 replies; 21+ messages in thread
From: Fenghua Yu @ 2026-08-31 17:22 UTC (permalink / raw)
To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
Will Deacon, Catalin Marinas, Shaopeng Tan, Chen Yu, Babu Moger,
Drew Fustini, Vikram Sethi, Shanker Donthineni, Newton Liu,
Richard Cheng
Cc: linux-kernel, linux-arm-kernel, Fenghua Yu
After enabling memory-level MSCs, the existing class-picking heuristics
still treat every candidate like an L3 cache MSC. That rejects valid
memory classes for MBWU counters and applies L3 egress checks to MBA
classes on CPU-less or system-wide affinities, leaving no monitor or
control class selected on affected platforms.
Adjust the heuristics so memory classes can back MBWU directly, L3
topology matching is only required for level-3 MBA candidates, and
traffic matching is skipped when the class already spans all CPUs.
Also tolerate components with no online CPUs once at least one
component has matched.
Signed-off-by: Shanker Donthineni <sdonthineni@nvidia.com>
Signed-off-by: Fenghua Yu <fenghuay@nvidia.com>
---
drivers/resctrl/mpam_resctrl.c | 22 +++++++++++++++++-----
1 file changed, 17 insertions(+), 5 deletions(-)
diff --git a/drivers/resctrl/mpam_resctrl.c b/drivers/resctrl/mpam_resctrl.c
index 47a7b18d841d..72302f6021d7 100644
--- a/drivers/resctrl/mpam_resctrl.c
+++ b/drivers/resctrl/mpam_resctrl.c
@@ -865,10 +865,12 @@ static bool topology_matches_l3(struct mpam_class *victim)
{
int cpu, err;
struct mpam_component *victim_iter;
+ bool matched_once = false;
lockdep_assert_cpus_held();
cpumask_var_t __free(free_cpumask_var) tmp_cpumask = CPUMASK_VAR_NULL;
+
if (!alloc_cpumask_var(&tmp_cpumask, GFP_KERNEL))
return false;
@@ -882,8 +884,11 @@ static bool topology_matches_l3(struct mpam_class *victim)
}
cpu = cpumask_any_and(&victim_iter->affinity, cpu_online_mask);
- if (WARN_ON_ONCE(cpu >= nr_cpu_ids))
+ if (WARN_ON_ONCE(cpu >= nr_cpu_ids)) {
+ if (matched_once)
+ continue;
return false;
+ }
cpumask_clear(tmp_cpumask);
err = find_l3_equivalent_bitmask(cpu, tmp_cpumask);
@@ -903,6 +908,7 @@ static bool topology_matches_l3(struct mpam_class *victim)
return false;
}
+ matched_once = true;
}
return true;
@@ -1041,13 +1047,15 @@ static void mpam_resctrl_pick_mba(void)
continue;
}
- if (!topology_matches_l3(class)) {
+ if (class->level == 3 && !topology_matches_l3(class)) {
pr_debug("class %u topology doesn't match L3\n",
class->level);
continue;
}
- if (!traffic_matches_l3(class)) {
+ /* Check memory at egress from L3 for MSC with L3 */
+ if (!cpumask_equal(&class->affinity, cpu_possible_mask) &&
+ !traffic_matches_l3(class)) {
pr_debug("class %u traffic doesn't match L3 egress\n",
class->level);
continue;
@@ -1175,8 +1183,9 @@ static void mpam_resctrl_pick_counters(void)
}
if (class_has_usable_mbwu(class) &&
- topology_matches_l3(class) &&
- traffic_matches_l3(class)) {
+ ((class->type == MPAM_CLASS_MEMORY) ||
+ (topology_matches_l3(class) &&
+ traffic_matches_l3(class)))) {
pr_debug("class %u has usable MBWU, and matches L3 topology and traffic\n",
class->level);
@@ -1314,6 +1323,9 @@ static int mpam_resctrl_pick_domain_id(int cpu, struct mpam_component *comp)
if (class->type == MPAM_CLASS_CACHE)
return comp->comp_id;
+ if (mpam_class_memory(class))
+ return comp->comp_id;
+
if (topology_matches_l3(class)) {
/* Use the corresponding L3 component ID as the domain ID */
int id = get_cpu_cacheinfo_id(cpu, 3);
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [PATCH RFC v2 08/19] arm_mpam: Include all MSC components during domain setup
2026-08-31 17:22 [PATCH RFC v2 00/19] arm,fs/resctrl: ARM MPAM MB_NODE support Fenghua Yu
` (6 preceding siblings ...)
2026-08-31 17:22 ` [PATCH RFC v2 07/19] arm_mpam: Refine L3 topology and class selection Fenghua Yu
@ 2026-08-31 17:22 ` Fenghua Yu
2026-08-31 17:22 ` [PATCH RFC v2 09/19] fs/resctrl: Take memory hotplug lock whenever taking CPU hotplug lock Fenghua Yu
` (11 subsequent siblings)
19 siblings, 0 replies; 21+ messages in thread
From: Fenghua Yu @ 2026-08-31 17:22 UTC (permalink / raw)
To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
Will Deacon, Catalin Marinas, Shaopeng Tan, Chen Yu, Babu Moger,
Drew Fustini, Vikram Sethi, Shanker Donthineni, Newton Liu,
Richard Cheng
Cc: linux-kernel, linux-arm-kernel, Fenghua Yu
Iterate all MSC components per CPU when creating and looking up control
and monitor domains on the controls branch. Key monitor domains by
component, support MBA mon_capable lookup, and report mon capability
from any L3 or MBA monitor resource.
Signed-off-by: Fenghua Yu <fenghuay@nvidia.com>
---
drivers/resctrl/mpam_resctrl.c | 197 +++++++++++++++++++--------------
1 file changed, 112 insertions(+), 85 deletions(-)
diff --git a/drivers/resctrl/mpam_resctrl.c b/drivers/resctrl/mpam_resctrl.c
index 72302f6021d7..9a02bc0e091d 100644
--- a/drivers/resctrl/mpam_resctrl.c
+++ b/drivers/resctrl/mpam_resctrl.c
@@ -95,11 +95,27 @@ static bool mpam_class_memory(struct mpam_class *class)
bool resctrl_arch_mon_capable(void)
{
- struct mpam_resctrl_res *res = &mpam_resctrl_controls[RDT_RESOURCE_L3];
- struct rdt_resource *l3 = &res->resctrl_res;
+ enum resctrl_event_id eventid;
+ struct mpam_resctrl_mon *mon;
+ struct mpam_resctrl_res *res;
+ struct rdt_resource *r;
+
+ for_each_mpam_resctrl_mon(mon, eventid) {
+ if (!mon->class)
+ continue; // dummy resource
+
+ if (mpam_class_memory(mon->class))
+ res = &mpam_resctrl_controls[RDT_RESOURCE_MBA];
+ else
+ res = &mpam_resctrl_controls[RDT_RESOURCE_L3];
+
+ r = &res->resctrl_res;
- /* All monitors are presented as being on the L3 cache */
- return l3->mon_capable;
+ if (r->mon_capable)
+ return true;
+ }
+
+ return false;
}
bool resctrl_arch_is_evt_configurable(enum resctrl_event_id evt)
@@ -1731,13 +1747,12 @@ static struct mpam_component *find_component(struct mpam_class *class, int cpu)
static struct mpam_resctrl_dom *
mpam_resctrl_alloc_ctrl_domain(unsigned int cpu, struct mpam_resctrl_res *res,
- struct resctrl_ctrl *ctrl)
+ struct resctrl_ctrl *ctrl,
+ struct mpam_component *comp)
{
int err;
struct mpam_resctrl_dom *dom;
struct rdt_ctrl_domain *ctrl_d;
- struct mpam_class *class = res->class;
- struct mpam_component *comp_iter, *ctrl_comp;
struct rdt_resource *r = &res->resctrl_res;
lockdep_assert_held(&domain_list_lock);
@@ -1745,28 +1760,17 @@ mpam_resctrl_alloc_ctrl_domain(unsigned int cpu, struct mpam_resctrl_res *res,
if (!r->alloc_capable)
return ERR_PTR(-EINVAL);
- ctrl_comp = NULL;
- guard(srcu)(&mpam_srcu);
- list_for_each_entry_srcu(comp_iter, &class->components, class_list,
- srcu_read_lock_held(&mpam_srcu)) {
- if (cpumask_test_cpu(cpu, &comp_iter->affinity)) {
- ctrl_comp = comp_iter;
- break;
- }
- }
-
- /* class has no component for this CPU */
- if (WARN_ON_ONCE(!ctrl_comp))
+ if (WARN_ON_ONCE(!comp))
return ERR_PTR(-EINVAL);
dom = kzalloc_node(sizeof(*dom), GFP_KERNEL, cpu_to_node(cpu));
if (!dom)
return ERR_PTR(-ENOMEM);
- dom->ctrl_comp = ctrl_comp;
+ dom->ctrl_comp = comp;
ctrl_d = &dom->resctrl_ctrl_dom;
- mpam_resctrl_domain_hdr_init(cpu, ctrl_comp, r->rid, &ctrl_d->hdr);
+ mpam_resctrl_domain_hdr_init(cpu, comp, r->rid, &ctrl_d->hdr);
ctrl_d->hdr.type = RESCTRL_CTRL_DOMAIN;
err = resctrl_online_ctrl_domain(r, ctrl, ctrl_d);
if (err)
@@ -1784,13 +1788,12 @@ mpam_resctrl_alloc_ctrl_domain(unsigned int cpu, struct mpam_resctrl_res *res,
}
static struct mpam_resctrl_dom *
-mpam_resctrl_alloc_mon_domain(unsigned int cpu, struct mpam_resctrl_res *res)
+mpam_resctrl_alloc_mon_domain(unsigned int cpu, struct mpam_resctrl_res *res,
+ struct mpam_component *comp)
{
int err;
struct mpam_resctrl_dom *dom;
struct rdt_l3_mon_domain *mon_d;
- struct mpam_class *class = res->class;
- struct mpam_component *comp_iter, *ctrl_comp;
struct rdt_resource *r = &res->resctrl_res;
struct mpam_component *any_mon_comp = NULL;
struct mpam_resctrl_mon *mon;
@@ -1801,24 +1804,14 @@ mpam_resctrl_alloc_mon_domain(unsigned int cpu, struct mpam_resctrl_res *res)
if (!r->mon_capable)
return ERR_PTR(-EINVAL);
- ctrl_comp = NULL;
- guard(srcu)(&mpam_srcu);
- list_for_each_entry_srcu(comp_iter, &class->components, class_list,
- srcu_read_lock_held(&mpam_srcu)) {
- if (cpumask_test_cpu(cpu, &comp_iter->affinity)) {
- ctrl_comp = comp_iter;
- break;
- }
- }
-
- /* class has no component for this CPU */
- if (WARN_ON_ONCE(!ctrl_comp))
+ if (WARN_ON_ONCE(!comp))
return ERR_PTR(-EINVAL);
dom = kzalloc_node(sizeof(*dom), GFP_KERNEL, cpu_to_node(cpu));
if (!dom)
return ERR_PTR(-ENOMEM);
+ dom->ctrl_comp = comp;
/*
* Even if the monitor domain is backed by a different
@@ -1833,7 +1826,7 @@ mpam_resctrl_alloc_mon_domain(unsigned int cpu, struct mpam_resctrl_res *res)
if (!mon->class)
continue; // dummy resource
- mon_comp = find_component(mon->class, cpu);
+ mon_comp = comp ? comp : find_component(mon->class, cpu);
dom->mon_comp[eventid] = mon_comp;
if (mon_comp)
any_mon_comp = mon_comp;
@@ -1870,25 +1863,32 @@ mpam_resctrl_alloc_mon_domain(unsigned int cpu, struct mpam_resctrl_res *res)
* for anything that is not a cache.
*/
static struct mpam_resctrl_dom *
-mpam_resctrl_get_mon_domain_from_cpu(int cpu, struct mpam_resctrl_res *res)
+mpam_resctrl_get_mon_domain_from_cpu(int cpu, struct mpam_resctrl_res *res,
+ struct mpam_component *comp)
{
- int cache_id;
struct mpam_resctrl_dom *dom;
struct rdt_resource *r = &res->resctrl_res;
+ int id;
lockdep_assert_cpus_held();
- if (r->rid != RDT_RESOURCE_L3)
- return ERR_PTR(-EINVAL);
+ if (!r->mon_capable || !res->class || !comp)
+ return NULL;
- if (!res->class)
- return ERR_PTR(-EINVAL);
- cache_id = get_cpu_cacheinfo_id(cpu, 3);
- if (cache_id < 0)
- return ERR_PTR(-EINVAL);
+ if (r->rid == RDT_RESOURCE_L3) {
+ id = get_cpu_cacheinfo_id(cpu, 3);
+ if (id < 0)
+ return NULL;
+ } else if (r->rid == RDT_RESOURCE_MBA) {
+ id = comp->comp_id;
+ } else {
+ return NULL;
+ }
- list_for_each_entry_rcu(dom, &res->resctrl_res.mon_domains, resctrl_mon_dom.hdr.list) {
- if (dom->resctrl_mon_dom.hdr.id == cache_id)
+ list_for_each_entry_rcu(dom, &r->mon_domains, resctrl_mon_dom.hdr.list) {
+ if (dom->ctrl_comp != comp)
+ continue;
+ if (dom->resctrl_mon_dom.hdr.id == id)
return dom;
}
@@ -1896,14 +1896,19 @@ mpam_resctrl_get_mon_domain_from_cpu(int cpu, struct mpam_resctrl_res *res)
}
static struct mpam_resctrl_dom *
-mpam_resctrl_get_ctrl_domain_from_cpu(int cpu, struct resctrl_ctrl *ctrl)
+mpam_resctrl_get_ctrl_domain_from_cpu(int cpu, struct mpam_resctrl_res *res,
+ struct resctrl_ctrl *ctrl,
+ struct mpam_component *comp)
{
struct mpam_resctrl_dom *dom;
lockdep_assert_cpus_held();
+ if (!comp)
+ return NULL;
+
list_for_each_entry_rcu(dom, &ctrl->domains, resctrl_ctrl_dom.hdr.list) {
- if (cpumask_test_cpu(cpu, &dom->ctrl_comp->affinity))
+ if (dom->ctrl_comp == comp)
return dom;
}
@@ -1914,6 +1919,7 @@ int mpam_resctrl_online_cpu(unsigned int cpu)
{
struct mpam_resctrl_res *res;
enum resctrl_res_level rid;
+ struct mpam_component *comp;
guard(mutex)(&domain_list_lock);
for_each_mpam_resctrl_control(res, rid) {
@@ -1924,30 +1930,40 @@ int mpam_resctrl_online_cpu(unsigned int cpu)
if (!res->class)
continue; // dummy_resource;
- if (r->alloc_capable) {
- for_each_resource_ctrl(ctrl, r) {
- dom = mpam_resctrl_get_ctrl_domain_from_cpu(cpu, ctrl);
- if (!dom) {
- dom = mpam_resctrl_alloc_ctrl_domain(cpu, res, ctrl);
+ guard(srcu)(&mpam_srcu);
+ list_for_each_entry_srcu(comp, &res->class->components, class_list,
+ srcu_read_lock_held(&mpam_srcu)) {
+ if (!cpumask_test_cpu(cpu, &comp->affinity))
+ continue;
+
+ if (r->alloc_capable) {
+ for_each_resource_ctrl(ctrl, r) {
+ dom = mpam_resctrl_get_ctrl_domain_from_cpu(cpu, res,
+ ctrl, comp);
+ if (!dom) {
+ dom = mpam_resctrl_alloc_ctrl_domain(cpu, res,
+ ctrl, comp);
+ } else {
+ struct rdt_ctrl_domain *ctrl_d =
+ &dom->resctrl_ctrl_dom;
+
+ mpam_resctrl_online_domain_hdr(cpu, &ctrl_d->hdr);
+ }
if (IS_ERR(dom))
return PTR_ERR(dom);
+ }
+ }
+ if (r->mon_capable) {
+ dom = mpam_resctrl_get_mon_domain_from_cpu(cpu, res, comp);
+ if (!dom) {
+ dom = mpam_resctrl_alloc_mon_domain(cpu, res, comp);
} else {
- struct rdt_ctrl_domain *ctrl_d = &dom->resctrl_ctrl_dom;
+ struct rdt_l3_mon_domain *mon_d = &dom->resctrl_mon_dom;
- mpam_resctrl_online_domain_hdr(cpu, &ctrl_d->hdr);
+ mpam_resctrl_online_domain_hdr(cpu, &mon_d->hdr);
}
- }
- }
- if (r->mon_capable) {
- dom = mpam_resctrl_get_mon_domain_from_cpu(cpu, res);
- if (!dom) {
- dom = mpam_resctrl_alloc_mon_domain(cpu, res);
if (IS_ERR(dom))
return PTR_ERR(dom);
- } else {
- struct rdt_l3_mon_domain *mon_d = &dom->resctrl_mon_dom;
-
- mpam_resctrl_online_domain_hdr(cpu, &mon_d->hdr);
}
}
}
@@ -1961,6 +1977,7 @@ void mpam_resctrl_offline_cpu(unsigned int cpu)
{
struct mpam_resctrl_res *res;
enum resctrl_res_level rid;
+ struct mpam_component *comp;
resctrl_offline_cpu(cpu);
@@ -1976,31 +1993,41 @@ void mpam_resctrl_offline_cpu(unsigned int cpu)
if (!res->class)
continue; // dummy resource
- if (r->alloc_capable) {
- for_each_resource_ctrl(ctrl, r) {
- dom = mpam_resctrl_get_ctrl_domain_from_cpu(cpu, ctrl);
+ guard(srcu)(&mpam_srcu);
+ list_for_each_entry_srcu(comp, &res->class->components, class_list,
+ srcu_read_lock_held(&mpam_srcu)) {
+ if (!cpumask_test_cpu(cpu, &comp->affinity))
+ continue;
+
+ if (r->alloc_capable) {
+ for_each_resource_ctrl(ctrl, r) {
+ dom = mpam_resctrl_get_ctrl_domain_from_cpu(cpu, res,
+ ctrl, comp);
+ if (WARN_ON_ONCE(!dom))
+ continue;
+ ctrl_d = &dom->resctrl_ctrl_dom;
+ dom_empty = mpam_resctrl_offline_domain_hdr(cpu,
+ &ctrl_d->hdr);
+ if (dom_empty) {
+ resctrl_offline_ctrl_domain(&res->resctrl_res, ctrl,
+ ctrl_d);
+ kfree(dom);
+ }
+ }
+ }
+
+ if (r->mon_capable) {
+ dom = mpam_resctrl_get_mon_domain_from_cpu(cpu, res, comp);
if (WARN_ON_ONCE(!dom))
continue;
- ctrl_d = &dom->resctrl_ctrl_dom;
- dom_empty = mpam_resctrl_offline_domain_hdr(cpu, &ctrl_d->hdr);
+ mon_d = &dom->resctrl_mon_dom;
+ dom_empty = mpam_resctrl_offline_domain_hdr(cpu, &mon_d->hdr);
if (dom_empty) {
- resctrl_offline_ctrl_domain(&res->resctrl_res, ctrl, ctrl_d);
+ resctrl_offline_mon_domain(&res->resctrl_res, &mon_d->hdr);
kfree(dom);
}
}
}
-
- if (r->mon_capable) {
- dom = mpam_resctrl_get_mon_domain_from_cpu(cpu, res);
- if (WARN_ON_ONCE(!dom))
- continue;
- mon_d = &dom->resctrl_mon_dom;
- dom_empty = mpam_resctrl_offline_domain_hdr(cpu, &mon_d->hdr);
- if (dom_empty) {
- resctrl_offline_mon_domain(&res->resctrl_res, &mon_d->hdr);
- kfree(dom);
- }
- }
}
}
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [PATCH RFC v2 09/19] fs/resctrl: Take memory hotplug lock whenever taking CPU hotplug lock
2026-08-31 17:22 [PATCH RFC v2 00/19] arm,fs/resctrl: ARM MPAM MB_NODE support Fenghua Yu
` (7 preceding siblings ...)
2026-08-31 17:22 ` [PATCH RFC v2 08/19] arm_mpam: Include all MSC components during domain setup Fenghua Yu
@ 2026-08-31 17:22 ` Fenghua Yu
2026-08-31 17:22 ` [PATCH RFC v2 10/19] arm_mpam: Handle CPU-less numa nodes Fenghua Yu
` (10 subsequent siblings)
19 siblings, 0 replies; 21+ messages in thread
From: Fenghua Yu @ 2026-08-31 17:22 UTC (permalink / raw)
To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
Will Deacon, Catalin Marinas, Shaopeng Tan, Chen Yu, Babu Moger,
Drew Fustini, Vikram Sethi, Shanker Donthineni, Newton Liu,
Richard Cheng
Cc: linux-kernel, linux-arm-kernel, Fenghua Yu
From: James Morse <james.morse@arm.com>
resctrl takes the read side CPU hotplug lock whenever it is working
with the list of domains. This prevents a CPU being brought online
and the list being modified while resctrl is walking the list, or
picking CPUs from the CPU masks.
If resctrl domains for CPU-less NUMA nodes are to be supported, this
would not be enough to prevent the domain list form being modified as
a NUMA node can come online with only memory.
Take the memory hotplug lock whenever the CPU hotplug lock is taken.
Signed-off-by: James Morse <james.morse@arm.com>
Signed-off-by: Fenghua Yu <fenghuay@nvidia.com>
---
fs/resctrl/pseudo_lock.c | 3 +++
fs/resctrl/rdtgroup.c | 13 +++++++++++++
2 files changed, 16 insertions(+)
diff --git a/fs/resctrl/pseudo_lock.c b/fs/resctrl/pseudo_lock.c
index 17b99a075eaf..c865913cb534 100644
--- a/fs/resctrl/pseudo_lock.c
+++ b/fs/resctrl/pseudo_lock.c
@@ -16,6 +16,7 @@
#include <linux/cpumask.h>
#include <linux/debugfs.h>
#include <linux/kthread.h>
+#include <linux/memory_hotplug.h>
#include <linux/mman.h>
#include <linux/pm_qos.h>
#include <linux/resctrl.h>
@@ -718,6 +719,7 @@ static int pseudo_lock_measure_cycles(struct rdtgroup *rdtgrp, int sel)
int ret = -1;
cpus_read_lock();
+ get_online_mems();
mutex_lock(&rdtgroup_mutex);
if (rdtgrp->flags & RDT_DELETED) {
@@ -765,6 +767,7 @@ static int pseudo_lock_measure_cycles(struct rdtgroup *rdtgrp, int sel)
out:
mutex_unlock(&rdtgroup_mutex);
+ put_online_mems();
cpus_read_unlock();
return ret;
}
diff --git a/fs/resctrl/rdtgroup.c b/fs/resctrl/rdtgroup.c
index d60868f7d5e7..a16e35b587c4 100644
--- a/fs/resctrl/rdtgroup.c
+++ b/fs/resctrl/rdtgroup.c
@@ -18,6 +18,7 @@
#include <linux/fs_parser.h>
#include <linux/sysfs.h>
#include <linux/kernfs.h>
+#include <linux/memory_hotplug.h>
#include <linux/once.h>
#include <linux/resctrl.h>
#include <linux/seq_buf.h>
@@ -3238,6 +3239,7 @@ struct rdtgroup *rdtgroup_kn_lock_live(struct kernfs_node *kn)
rdtgroup_kn_get(rdtgrp, kn);
cpus_read_lock();
+ get_online_mems();
mutex_lock(&rdtgroup_mutex);
rdt_last_cmd_clear();
@@ -3265,6 +3267,7 @@ void rdtgroup_kn_unlock(struct kernfs_node *kn)
return;
mutex_unlock(&rdtgroup_mutex);
+ put_online_mems();
cpus_read_unlock();
rdtgroup_kn_put(rdtgrp, kn);
@@ -3299,6 +3302,7 @@ bool info_kn_lock(struct kernfs_node *kn)
{
kernfs_break_active_protection(kn);
cpus_read_lock();
+ get_online_mems();
mutex_lock(&rdtgroup_mutex);
/*
@@ -3307,6 +3311,7 @@ bool info_kn_lock(struct kernfs_node *kn)
*/
if (!rdtgroup_default.kn || !is_active_resctrl_node(kn)) {
mutex_unlock(&rdtgroup_mutex);
+ put_online_mems();
cpus_read_unlock();
kernfs_unbreak_active_protection(kn);
return false;
@@ -3318,6 +3323,7 @@ bool info_kn_lock(struct kernfs_node *kn)
void info_kn_unlock(struct kernfs_node *kn)
{
mutex_unlock(&rdtgroup_mutex);
+ put_online_mems();
cpus_read_unlock();
kernfs_unbreak_active_protection(kn);
}
@@ -3671,6 +3677,7 @@ static void resctrl_unmount(void)
struct rdt_resource *r;
cpus_read_lock();
+ get_online_mems();
mutex_lock(&rdtgroup_mutex);
rdt_disable_ctx();
@@ -3686,6 +3693,7 @@ static void resctrl_unmount(void)
resctrl_arch_disable_mon();
resctrl_mounted = false;
mutex_unlock(&rdtgroup_mutex);
+ put_online_mems();
cpus_read_unlock();
}
@@ -3701,6 +3709,7 @@ static int rdt_get_tree(struct fs_context *fc)
DO_ONCE_SLEEPABLE(resctrl_arch_pre_mount);
cpus_read_lock();
+ get_online_mems();
mutex_lock(&rdtgroup_mutex);
/*
* resctrl file system can only be mounted once.
@@ -3804,6 +3813,7 @@ static int rdt_get_tree(struct fs_context *fc)
rdt_last_cmd_clear();
mutex_unlock(&rdtgroup_mutex);
+ put_online_mems();
cpus_read_unlock();
ret = kernfs_get_tree(fc);
@@ -3836,6 +3846,7 @@ static int rdt_get_tree(struct fs_context *fc)
rdtgroup_destroy_root();
out:
mutex_unlock(&rdtgroup_mutex);
+ put_online_mems();
cpus_read_unlock();
return ret;
}
@@ -5502,12 +5513,14 @@ static bool resctrl_online_domains_exist(void)
void resctrl_exit(void)
{
cpus_read_lock();
+ get_online_mems();
WARN_ON_ONCE(resctrl_online_domains_exist());
mutex_lock(&rdtgroup_mutex);
resctrl_fs_teardown();
mutex_unlock(&rdtgroup_mutex);
+ put_online_mems();
cpus_read_unlock();
debugfs_remove_recursive(debugfs_resctrl);
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [PATCH RFC v2 10/19] arm_mpam: Handle CPU-less numa nodes
2026-08-31 17:22 [PATCH RFC v2 00/19] arm,fs/resctrl: ARM MPAM MB_NODE support Fenghua Yu
` (8 preceding siblings ...)
2026-08-31 17:22 ` [PATCH RFC v2 09/19] fs/resctrl: Take memory hotplug lock whenever taking CPU hotplug lock Fenghua Yu
@ 2026-08-31 17:22 ` Fenghua Yu
2026-08-31 17:22 ` [PATCH RFC v2 11/19] arm_mpam: Emulate MB control with node-scoped MB_NODE control Fenghua Yu
` (9 subsequent siblings)
19 siblings, 0 replies; 21+ messages in thread
From: Fenghua Yu @ 2026-08-31 17:22 UTC (permalink / raw)
To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
Will Deacon, Catalin Marinas, Shaopeng Tan, Chen Yu, Babu Moger,
Drew Fustini, Vikram Sethi, Shanker Donthineni, Newton Liu,
Richard Cheng
Cc: linux-kernel, linux-arm-kernel, Fenghua Yu
On some ARM servers a NUMA node can expose a memory MPAM MSC without
any local CPUs (for example, a memory-only node that still participates
in bandwidth control). mpam_ris_get_affinity() derives affinity from
the component's NUMA node ID, so these MSCs end up with an empty CPU
mask and are never registered. Without a fallback, resctrl cannot see
or control bandwidth on those nodes.
When the derived cpumask is empty, fall back to cpu_possible_mask so
the MSC can be managed and traffic from remote CPUs is still accounted
for. Track the borrowed case with ris->cpu_less and keep that mask out
of class->affinity on create and destroy: those CPUs already contribute
through CPU-ful nodes in the class, and subtracting the borrowed mask
later would remove CPUs other live nodes still depend on. Emit an
informational message reporting each CPU-less node when this happens.
Signed-off-by: Shanker Donthineni <sdonthineni@nvidia.com>
Signed-off-by: Fenghua Yu <fenghuay@nvidia.com>
---
drivers/resctrl/mpam_devices.c | 59 ++++++++++++++++++++++++++++-----
drivers/resctrl/mpam_internal.h | 8 +++++
2 files changed, 59 insertions(+), 8 deletions(-)
diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
index b69f99488111..cb6096e9d2cf 100644
--- a/drivers/resctrl/mpam_devices.c
+++ b/drivers/resctrl/mpam_devices.c
@@ -508,10 +508,13 @@ static void get_cpumask_from_node_id(u32 node_id, cpumask_t *affinity)
static int mpam_ris_get_affinity(struct mpam_msc *msc, cpumask_t *affinity,
enum mpam_class_types type,
struct mpam_class *class,
- struct mpam_component *comp)
+ struct mpam_component *comp,
+ bool *cpu_less)
{
int err;
+ *cpu_less = false;
+
switch (type) {
case MPAM_CLASS_CACHE:
err = mpam_get_cpumask_from_cache_id(comp->comp_id, class->level,
@@ -528,7 +531,17 @@ static int mpam_ris_get_affinity(struct mpam_msc *msc, cpumask_t *affinity,
break;
case MPAM_CLASS_MEMORY:
get_cpumask_from_node_id(comp->comp_id, affinity);
- /* affinity may be empty for CPU-less memory nodes */
+ /*
+ * A CPU-less memory node has no local CPUs. Borrow the CPUs that
+ * can reach the MSC so the node still gets a resctrl domain, and
+ * flag it so the borrowed mask is kept out of class->affinity.
+ */
+ if (cpumask_empty(affinity)) {
+ dev_info(&msc->pdev->dev,
+ "CPU-less numa node %u\n", comp->comp_id);
+ cpumask_copy(affinity, cpu_possible_mask);
+ *cpu_less = true;
+ }
break;
case MPAM_CLASS_UNKNOWN:
return 0;
@@ -582,7 +595,8 @@ static int mpam_ris_create_locked(struct mpam_msc *msc, u8 ris_idx,
return PTR_ERR(vmsc);
}
- err = mpam_ris_get_affinity(msc, &ris->affinity, type, class, comp);
+ err = mpam_ris_get_affinity(msc, &ris->affinity, type, class, comp,
+ &ris->cpu_less);
if (err) {
if (list_empty(&vmsc->ris))
mpam_vmsc_destroy(vmsc);
@@ -594,8 +608,24 @@ static int mpam_ris_create_locked(struct mpam_msc *msc, u8 ris_idx,
INIT_LIST_HEAD_RCU(&ris->vmsc_list);
ris->vmsc = vmsc;
+ /*
+ * comp->affinity must include the borrowed mask for CPU-less nodes:
+ * mpam_resctrl_online_cpu() matches CPUs against comp->affinity to
+ * create per-component domains. Overlap between CPU-less and CPU-ful
+ * components is expected; each match gets its own domain keyed by
+ * comp->comp_id, not by first-match lookup.
+ *
+ * class->affinity is different: it tracks CPUs with a local MSC in
+ * this class. A CPU-less node borrows the CPUs that can reach its MSC
+ * (see mpam_ris_get_affinity()) only so that it gets a per-component
+ * resctrl domain. Those CPUs already contribute to class->affinity
+ * through their own CPU-ful node, so folding the borrowed mask in here
+ * would let mpam_ris_destroy() later subtract CPUs that other live
+ * nodes in the class still depend on.
+ */
cpumask_or(&comp->affinity, &comp->affinity, &ris->affinity);
- cpumask_or(&class->affinity, &class->affinity, &ris->affinity);
+ if (!ris->cpu_less)
+ cpumask_or(&class->affinity, &class->affinity, &ris->affinity);
list_add_rcu(&ris->vmsc_list, &vmsc->ris);
list_add_rcu(&ris->msc_list, &msc->ris);
@@ -612,11 +642,24 @@ static void mpam_ris_destroy(struct mpam_msc_ris *ris)
lockdep_assert_held(&mpam_list_lock);
/*
- * It is assumed affinities don't overlap. If they do the class becomes
- * unusable immediately.
+ * A CPU-less node's borrowed affinity was deliberately never folded into
+ * class->affinity (see mpam_ris_create_locked()), so it must not be
+ * subtracted here: doing so would remove CPUs that other live nodes in
+ * the class still rely on.
+ *
+ * Every RIS of a CPU-less component borrowed cpu_possible_mask masked by
+ * its own msc->accessibility (see mpam_ris_get_affinity()), so these
+ * borrowed masks overlap across sibling RIS. Such a mask must not be
+ * subtracted from comp->affinity either: a component backed by more than
+ * one MSC keeps a live sibling RIS after this one is destroyed, and
+ * subtracting the overlapping mask would wrongly empty comp->affinity
+ * while that sibling still needs it. The borrowed affinity is released
+ * when the component itself is torn down.
*/
- cpumask_andnot(&class->affinity, &class->affinity, &ris->affinity);
- cpumask_andnot(&comp->affinity, &comp->affinity, &ris->affinity);
+ if (!ris->cpu_less) {
+ cpumask_andnot(&class->affinity, &class->affinity, &ris->affinity);
+ cpumask_andnot(&comp->affinity, &comp->affinity, &ris->affinity);
+ }
clear_bit(ris->ris_idx, &msc->ris_idxs);
list_del_rcu(&ris->msc_list);
list_del_rcu(&ris->vmsc_list);
diff --git a/drivers/resctrl/mpam_internal.h b/drivers/resctrl/mpam_internal.h
index dfa3ceb734d4..1c9e0e8b78a9 100644
--- a/drivers/resctrl/mpam_internal.h
+++ b/drivers/resctrl/mpam_internal.h
@@ -369,6 +369,14 @@ struct mpam_msc_ris {
struct mpam_props props;
bool in_reset_state;
+ /*
+ * A CPU-less memory node has no CPUs of its own, so this RIS borrows the
+ * CPUs that can reach its MSC (see mpam_ris_get_affinity()) purely so
+ * the node still gets a resctrl domain. When set, the borrowed mask in
+ * @affinity must not be folded into the owning class's affinity.
+ */
+ bool cpu_less;
+
cpumask_t affinity;
/* member of mpam_vmsc:ris */
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [PATCH RFC v2 11/19] arm_mpam: Emulate MB control with node-scoped MB_NODE control
2026-08-31 17:22 [PATCH RFC v2 00/19] arm,fs/resctrl: ARM MPAM MB_NODE support Fenghua Yu
` (9 preceding siblings ...)
2026-08-31 17:22 ` [PATCH RFC v2 10/19] arm_mpam: Handle CPU-less numa nodes Fenghua Yu
@ 2026-08-31 17:22 ` Fenghua Yu
2026-08-31 17:22 ` [PATCH RFC v2 12/19] arm_mpam: resctrl: Add NUMA node notifier for domain online/offline Fenghua Yu
` (8 subsequent siblings)
19 siblings, 0 replies; 21+ messages in thread
From: Fenghua Yu @ 2026-08-31 17:22 UTC (permalink / raw)
To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
Will Deacon, Catalin Marinas, Shaopeng Tan, Chen Yu, Babu Moger,
Drew Fustini, Vikram Sethi, Shanker Donthineni, Newton Liu,
Richard Cheng
Cc: linux-kernel, linux-arm-kernel, Fenghua Yu
On memory-class MSCs the MB bandwidth control is node-scoped. Add a
node-scoped MB_NODE control that emulates the default MB control by
adding it to the default control's resctrl_ctrl::emulated_by list, so
the legacy "MB:" schemata line keeps working while user space can switch
the resource to native mode to drive MB_NODE directly.
Introduce mpam_resctrl_ctrl_node() to identify memory-class resources
and use it when selecting the control scope, replacing the open-coded
assumption that MBA is always cache-scoped. A memory class has one
component per memory controller, so its control domains are node-scoped
and rdt_resource::ctrl_scope becomes RESCTRL_NODE.
An emulating control has its own control domains, so give the MPAM CPU
hotplug and domain update paths a walk of the emulated_by list.
resctrl_arch_get_config() and resctrl_arch_update_one() accept the
MB_NODE control name alongside the default one, both being backed by the
same MPAM bandwidth partitioning hardware.
Signed-off-by: Fenghua Yu <fenghuay@nvidia.com>
---
drivers/resctrl/mpam_resctrl.c | 265 ++++++++++++++++++++++++++-------
fs/resctrl/ctrlmondata.c | 1 +
include/linux/resctrl.h | 2 +
3 files changed, 211 insertions(+), 57 deletions(-)
diff --git a/drivers/resctrl/mpam_resctrl.c b/drivers/resctrl/mpam_resctrl.c
index 9a02bc0e091d..cd9bebf5f0c2 100644
--- a/drivers/resctrl/mpam_resctrl.c
+++ b/drivers/resctrl/mpam_resctrl.c
@@ -1264,20 +1264,108 @@ void resctrl_arch_config_cntr(struct rdt_resource *r, struct rdt_l3_mon_domain *
resctrl_arch_reset_cntr(r, d, closid, rmid, cntr_id, evtid);
}
+/*
+ * Bandwidth control on a memory-class MSC is node-scoped, unlike the
+ * cache-scoped control provided by an MSC in front of the L3.
+ */
+static bool mpam_resctrl_ctrl_node(struct rdt_resource *r)
+{
+ struct mpam_resctrl_res *res;
+
+ res = container_of(r, struct mpam_resctrl_res, resctrl_res);
+
+ return mpam_class_memory(res->class);
+}
+
+static bool mpam_resctrl_cache_has_mba(void)
+{
+ struct mpam_class *class = mpam_resctrl_controls[RDT_RESOURCE_L3].class;
+
+ if (!class)
+ return false;
+
+ return class_has_usable_mba(&class->props);
+}
+
+static void _mpam_resctrl_ctrl_init_mba(struct mpam_resctrl_ctrl *mpam_ctrl,
+ struct mpam_props *cprops,
+ enum resctrl_ctrl_name name)
+{
+ mpam_ctrl->r_ctrl.type = RESCTRL_CTRL_SCALAR;
+ mpam_ctrl->r_ctrl.name = name;
+ INIT_LIST_HEAD_RCU(&mpam_ctrl->r_ctrl.domains);
+ INIT_LIST_HEAD(&mpam_ctrl->r_ctrl.emulated_by);
+
+ __set_bit(RESCTRL_SCALAR_FLAG_LINEAR, mpam_ctrl->r_ctrl.scalar.flags);
+ mpam_ctrl->r_ctrl.scalar.min = get_mba_min(cprops);
+ mpam_ctrl->r_ctrl.scalar.max = MAX_MBA_BW;
+ mpam_ctrl->r_ctrl.scalar.reset_val = MAX_MBA_BW;
+ mpam_ctrl->r_ctrl.scalar.gran = get_mba_granularity(cprops);
+}
+
+static int mpam_resctrl_ctrl_init_mba(struct rdt_resource *r,
+ struct mpam_props *cprops)
+{
+ struct mpam_resctrl_ctrl *ctrl_def, *ctrl_node;
+
+ ctrl_def = kzalloc_obj(*ctrl_def);
+ if (!ctrl_def)
+ return -ENOMEM;
+
+ _mpam_resctrl_ctrl_init_mba(ctrl_def, cprops, RESCTRL_CTRL_NAME_DEF);
+ list_add(&ctrl_def->r_ctrl.entry, &r->controls);
+
+ if (!mpam_resctrl_ctrl_node(r)) {
+ r->ctrl_scope = RESCTRL_L3_CACHE;
+ return 0;
+ }
+
+ /*
+ * The control domains of a memory class follow the memory
+ * controllers, one per NUMA node.
+ */
+ r->ctrl_scope = RESCTRL_NODE;
+
+ /*
+ * When the L3 cache has usable bandwidth control the legacy MB control
+ * is backed by that hardware and needs no emulation.
+ */
+ if (mpam_resctrl_cache_has_mba())
+ return 0;
+
+ ctrl_node = kzalloc_obj(*ctrl_node);
+ if (!ctrl_node)
+ return -ENOMEM;
+
+ _mpam_resctrl_ctrl_init_mba(ctrl_node, cprops, RESCTRL_CTRL_NAME_NODE);
+
+ /*
+ * MB has no bandwidth hardware of its own, so back it with the
+ * node-scoped MB_NODE control. Legacy mode keeps the "MB:" schemata
+ * line for backward compatibility, native mode exposes MB_NODE
+ * instead.
+ */
+ list_add(&ctrl_node->r_ctrl.entry, &ctrl_def->r_ctrl.emulated_by);
+ r->ctrl_mode = RESCTRL_CTRL_MODE_LEGACY;
+
+ return 0;
+}
+
static int mpam_resctrl_control_init(struct mpam_resctrl_res *res)
{
struct mpam_class *class = res->class;
struct mpam_props *cprops = &class->props;
struct rdt_resource *r = &res->resctrl_res;
struct mpam_resctrl_ctrl *mpam_ctrl;
-
- mpam_ctrl = kzalloc_obj(*mpam_ctrl);
- if (!mpam_ctrl)
- return -ENOMEM;
+ int ret;
switch (r->rid) {
case RDT_RESOURCE_L2:
case RDT_RESOURCE_L3:
+ mpam_ctrl = kzalloc_obj(*mpam_ctrl);
+ if (!mpam_ctrl)
+ return -ENOMEM;
+
mpam_ctrl->r_ctrl.type = RESCTRL_CTRL_BITMAP;
mpam_ctrl->r_ctrl.name = RESCTRL_CTRL_NAME_DEF;
INIT_LIST_HEAD_RCU(&mpam_ctrl->r_ctrl.domains);
@@ -1308,20 +1396,11 @@ static int mpam_resctrl_control_init(struct mpam_resctrl_res *res)
r->alloc_capable = true;
break;
case RDT_RESOURCE_MBA:
- r->ctrl_scope = RESCTRL_L3_CACHE;
- mpam_ctrl->r_ctrl.type = RESCTRL_CTRL_SCALAR;
- mpam_ctrl->r_ctrl.name = RESCTRL_CTRL_NAME_DEF;
- INIT_LIST_HEAD_RCU(&mpam_ctrl->r_ctrl.domains);
- INIT_LIST_HEAD(&mpam_ctrl->r_ctrl.emulated_by);
+ ret = mpam_resctrl_ctrl_init_mba(r, cprops);
+ if (ret)
+ return ret;
r->bw_throttle_mode = THREAD_THROTTLE_UNDEFINED;
- __set_bit(RESCTRL_SCALAR_FLAG_LINEAR, mpam_ctrl->r_ctrl.scalar.flags);
- mpam_ctrl->r_ctrl.scalar.min = get_mba_min(cprops);
- mpam_ctrl->r_ctrl.scalar.max = MAX_MBA_BW;
- mpam_ctrl->r_ctrl.scalar.reset_val = MAX_MBA_BW;
- mpam_ctrl->r_ctrl.scalar.gran = get_mba_granularity(cprops);
- list_add(&mpam_ctrl->r_ctrl.entry, &r->controls);
-
r->name = "MB";
r->alloc_capable = true;
break;
@@ -1484,7 +1563,7 @@ u32 resctrl_arch_get_config(struct rdt_resource *r, struct resctrl_ctrl *ctrl,
struct mpam_props *cprops;
struct mpam_resctrl_res *res;
struct mpam_resctrl_dom *dom;
- enum mpam_device_features configured_by;
+ enum mpam_device_features configured_by = MPAM_FEATURE_LAST;
lockdep_assert_cpus_held();
@@ -1512,16 +1591,26 @@ u32 resctrl_arch_get_config(struct rdt_resource *r, struct resctrl_ctrl *ctrl,
configured_by = mpam_feat_cpor_part;
break;
case RDT_RESOURCE_MBA:
- if (mpam_has_feature(mpam_feat_mbw_max, cprops)) {
- configured_by = mpam_feat_mbw_max;
+ /*
+ * MB and its emulating MB_NODE control are both backed by the
+ * same MPAM bandwidth partitioning hardware.
+ */
+ switch (ctrl->name) {
+ case RESCTRL_CTRL_NAME_DEF:
+ case RESCTRL_CTRL_NAME_NODE:
+ if (mpam_has_feature(mpam_feat_mbw_max, cprops))
+ configured_by = mpam_feat_mbw_max;
+ break;
+ default:
break;
}
- fallthrough;
+ break;
default:
return resctrl_get_default_ctrlval(ctrl);
}
- if (!r->alloc_capable || partid >= resctrl_arch_get_num_closid(r) ||
+ if (configured_by == MPAM_FEATURE_LAST || !r->alloc_capable ||
+ partid >= resctrl_arch_get_num_closid(r) ||
!mpam_has_feature(configured_by, cfg))
return resctrl_get_default_ctrlval(ctrl);
@@ -1582,12 +1671,19 @@ int resctrl_arch_update_one(struct rdt_resource *r, struct resctrl_ctrl *ctrl,
mpam_set_feature(mpam_feat_cpor_part, &cfg);
break;
case RDT_RESOURCE_MBA:
- if (mpam_has_feature(mpam_feat_mbw_max, cprops)) {
+ switch (ctrl->name) {
+ case RESCTRL_CTRL_NAME_DEF:
+ case RESCTRL_CTRL_NAME_NODE:
+ if (!mpam_has_feature(mpam_feat_mbw_max, cprops))
+ return -EINVAL;
+
cfg.mbw_max = percent_to_mbw_max(cfg_val, cprops);
mpam_set_feature(mpam_feat_mbw_max, &cfg);
break;
+ default:
+ return -EINVAL;
}
- fallthrough;
+ break;
default:
return -EINVAL;
}
@@ -1650,9 +1746,17 @@ int resctrl_arch_update_domains(struct rdt_resource *r, u32 closid)
* controls? How to communicate to user space?
*/
for_each_resource_ctrl(ctrl, r) {
+ struct resctrl_ctrl *em_ctrl;
+
err = _resctrl_arch_update_domains(r, ctrl, closid);
if (err)
return err;
+
+ list_for_each_entry(em_ctrl, &ctrl->emulated_by, entry) {
+ err = _resctrl_arch_update_domains(r, em_ctrl, closid);
+ if (err)
+ return err;
+ }
}
return 0;
@@ -1915,6 +2019,81 @@ mpam_resctrl_get_ctrl_domain_from_cpu(int cpu, struct mpam_resctrl_res *res,
return NULL;
}
+static int mpam_resctrl_online_ctrl(unsigned int cpu, struct mpam_resctrl_res *res,
+ struct resctrl_ctrl *ctrl,
+ struct mpam_component *comp)
+{
+ struct mpam_resctrl_dom *dom;
+
+ dom = mpam_resctrl_get_ctrl_domain_from_cpu(cpu, res, ctrl, comp);
+ if (!dom) {
+ dom = mpam_resctrl_alloc_ctrl_domain(cpu, res, ctrl, comp);
+ if (IS_ERR(dom))
+ return PTR_ERR(dom);
+
+ return 0;
+ }
+
+ mpam_resctrl_online_domain_hdr(cpu, &dom->resctrl_ctrl_dom.hdr);
+
+ return 0;
+}
+
+static void mpam_resctrl_offline_ctrl(unsigned int cpu, struct mpam_resctrl_res *res,
+ struct resctrl_ctrl *ctrl,
+ struct mpam_component *comp)
+{
+ struct rdt_ctrl_domain *ctrl_d;
+ struct mpam_resctrl_dom *dom;
+
+ dom = mpam_resctrl_get_ctrl_domain_from_cpu(cpu, res, ctrl, comp);
+ if (WARN_ON_ONCE(!dom))
+ return;
+
+ ctrl_d = &dom->resctrl_ctrl_dom;
+ if (mpam_resctrl_offline_domain_hdr(cpu, &ctrl_d->hdr)) {
+ resctrl_offline_ctrl_domain(&res->resctrl_res, ctrl, ctrl_d);
+ kfree(dom);
+ }
+}
+
+/* Controls that emulate another control need domains of their own. */
+static int mpam_resctrl_online_ctrls(unsigned int cpu, struct mpam_resctrl_res *res,
+ struct mpam_component *comp)
+{
+ struct rdt_resource *r = &res->resctrl_res;
+ struct resctrl_ctrl *ctrl, *em_ctrl;
+ int err;
+
+ for_each_resource_ctrl(ctrl, r) {
+ err = mpam_resctrl_online_ctrl(cpu, res, ctrl, comp);
+ if (err)
+ return err;
+
+ list_for_each_entry(em_ctrl, &ctrl->emulated_by, entry) {
+ err = mpam_resctrl_online_ctrl(cpu, res, em_ctrl, comp);
+ if (err)
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+static void mpam_resctrl_offline_ctrls(unsigned int cpu, struct mpam_resctrl_res *res,
+ struct mpam_component *comp)
+{
+ struct rdt_resource *r = &res->resctrl_res;
+ struct resctrl_ctrl *ctrl, *em_ctrl;
+
+ for_each_resource_ctrl(ctrl, r) {
+ list_for_each_entry(em_ctrl, &ctrl->emulated_by, entry)
+ mpam_resctrl_offline_ctrl(cpu, res, em_ctrl, comp);
+
+ mpam_resctrl_offline_ctrl(cpu, res, ctrl, comp);
+ }
+}
+
int mpam_resctrl_online_cpu(unsigned int cpu)
{
struct mpam_resctrl_res *res;
@@ -1925,7 +2104,6 @@ int mpam_resctrl_online_cpu(unsigned int cpu)
for_each_mpam_resctrl_control(res, rid) {
struct mpam_resctrl_dom *dom;
struct rdt_resource *r = &res->resctrl_res;
- struct resctrl_ctrl *ctrl;
if (!res->class)
continue; // dummy_resource;
@@ -1937,21 +2115,10 @@ int mpam_resctrl_online_cpu(unsigned int cpu)
continue;
if (r->alloc_capable) {
- for_each_resource_ctrl(ctrl, r) {
- dom = mpam_resctrl_get_ctrl_domain_from_cpu(cpu, res,
- ctrl, comp);
- if (!dom) {
- dom = mpam_resctrl_alloc_ctrl_domain(cpu, res,
- ctrl, comp);
- } else {
- struct rdt_ctrl_domain *ctrl_d =
- &dom->resctrl_ctrl_dom;
-
- mpam_resctrl_online_domain_hdr(cpu, &ctrl_d->hdr);
- }
- if (IS_ERR(dom))
- return PTR_ERR(dom);
- }
+ int err = mpam_resctrl_online_ctrls(cpu, res, comp);
+
+ if (err)
+ return err;
}
if (r->mon_capable) {
dom = mpam_resctrl_get_mon_domain_from_cpu(cpu, res, comp);
@@ -1985,8 +2152,6 @@ void mpam_resctrl_offline_cpu(unsigned int cpu)
for_each_mpam_resctrl_control(res, rid) {
struct mpam_resctrl_dom *dom;
struct rdt_l3_mon_domain *mon_d;
- struct rdt_ctrl_domain *ctrl_d;
- struct resctrl_ctrl *ctrl;
bool dom_empty;
struct rdt_resource *r = &res->resctrl_res;
@@ -1999,22 +2164,8 @@ void mpam_resctrl_offline_cpu(unsigned int cpu)
if (!cpumask_test_cpu(cpu, &comp->affinity))
continue;
- if (r->alloc_capable) {
- for_each_resource_ctrl(ctrl, r) {
- dom = mpam_resctrl_get_ctrl_domain_from_cpu(cpu, res,
- ctrl, comp);
- if (WARN_ON_ONCE(!dom))
- continue;
- ctrl_d = &dom->resctrl_ctrl_dom;
- dom_empty = mpam_resctrl_offline_domain_hdr(cpu,
- &ctrl_d->hdr);
- if (dom_empty) {
- resctrl_offline_ctrl_domain(&res->resctrl_res, ctrl,
- ctrl_d);
- kfree(dom);
- }
- }
- }
+ if (r->alloc_capable)
+ mpam_resctrl_offline_ctrls(cpu, res, comp);
if (r->mon_capable) {
dom = mpam_resctrl_get_mon_domain_from_cpu(cpu, res, comp);
diff --git a/fs/resctrl/ctrlmondata.c b/fs/resctrl/ctrlmondata.c
index 4bc7090ad0c7..77a2f2e877a6 100644
--- a/fs/resctrl/ctrlmondata.c
+++ b/fs/resctrl/ctrlmondata.c
@@ -359,6 +359,7 @@ static const char * const resctrl_ctrl_name[] = {
[RESCTRL_CTRL_NAME_DEF] = "",
[RESCTRL_CTRL_NAME_MIN] = "MIN",
[RESCTRL_CTRL_NAME_MAX] = "MAX",
+ [RESCTRL_CTRL_NAME_NODE] = "NODE",
[RESCTRL_CTRL_NAME_REGION0_MIN] = "REGION0_MIN",
[RESCTRL_CTRL_NAME_REGION0_MAX] = "REGION0_MAX",
};
diff --git a/include/linux/resctrl.h b/include/linux/resctrl.h
index 195c0948b26d..b8df7489539f 100644
--- a/include/linux/resctrl.h
+++ b/include/linux/resctrl.h
@@ -398,6 +398,7 @@ struct resctrl_mon {
* has the same name as the resource.
* @RESCTRL_CTRL_NAME_MIN: "MIN"
* @RESCTRL_CTRL_NAME_MAX: "MAX"
+ * @RESCTRL_CTRL_NAME_NODE: "NODE"
* @RESCTRL_CTRL_NAME_REGION0_MIN: "REGION0_MIN"
* @RESCTRL_CTRL_NAME_REGION0_MAX: "REGION0_MAX"
*/
@@ -405,6 +406,7 @@ enum resctrl_ctrl_name {
RESCTRL_CTRL_NAME_DEF,
RESCTRL_CTRL_NAME_MIN,
RESCTRL_CTRL_NAME_MAX,
+ RESCTRL_CTRL_NAME_NODE,
RESCTRL_CTRL_NAME_REGION0_MIN,
RESCTRL_CTRL_NAME_REGION0_MAX,
RESCTRL_CTRL_NAME_LAST = RESCTRL_CTRL_NAME_REGION0_MAX
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [PATCH RFC v2 12/19] arm_mpam: resctrl: Add NUMA node notifier for domain online/offline
2026-08-31 17:22 [PATCH RFC v2 00/19] arm,fs/resctrl: ARM MPAM MB_NODE support Fenghua Yu
` (10 preceding siblings ...)
2026-08-31 17:22 ` [PATCH RFC v2 11/19] arm_mpam: Emulate MB control with node-scoped MB_NODE control Fenghua Yu
@ 2026-08-31 17:22 ` Fenghua Yu
2026-08-31 17:22 ` [PATCH RFC v2 13/19] resctrl: Add mbm_assign_scope_mode for native assignment file names Fenghua Yu
` (7 subsequent siblings)
19 siblings, 0 replies; 21+ messages in thread
From: Fenghua Yu @ 2026-08-31 17:22 UTC (permalink / raw)
To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
Will Deacon, Catalin Marinas, Shaopeng Tan, Chen Yu, Babu Moger,
Drew Fustini, Vikram Sethi, Shanker Donthineni, Newton Liu,
Richard Cheng
Cc: linux-kernel, linux-arm-kernel, Fenghua Yu
From: James Morse <james.morse@arm.com>
To expose resctrl resources that contain CPU-less NUMA domains, resctrl
needs to be told when a CPU-less NUMA domain comes online. This can't
be done with the cpuhp callbacks.
Add a memory hotplug notifier, and use this to create and destroy
resctrl domains.
Signed-off-by: James Morse <james.morse@arm.com>
Signed-off-by: Fenghua Yu <fenghuay@nvidia.com>
---
drivers/resctrl/mpam_resctrl.c | 343 ++++++++++++++++++++++++++++++++-
include/linux/memory.h | 1 +
2 files changed, 337 insertions(+), 7 deletions(-)
diff --git a/drivers/resctrl/mpam_resctrl.c b/drivers/resctrl/mpam_resctrl.c
index cd9bebf5f0c2..8bb313efc716 100644
--- a/drivers/resctrl/mpam_resctrl.c
+++ b/drivers/resctrl/mpam_resctrl.c
@@ -11,6 +11,8 @@
#include <linux/limits.h>
#include <linux/list.h>
#include <linux/math.h>
+#include <linux/memory.h>
+#include <linux/node.h>
#include <linux/printk.h>
#include <linux/rculist.h>
#include <linux/resctrl.h>
@@ -1775,16 +1777,25 @@ void resctrl_arch_reset_all_ctrls(struct rdt_resource *r)
mpam_reset_class_locked(res->class);
}
+static void mpam_resctrl_domain_hdr_init_mask(const struct cpumask *cpus,
+ int id_cpu,
+ struct mpam_component *comp,
+ enum resctrl_res_level rid,
+ struct rdt_domain_hdr *hdr)
+{
+ INIT_LIST_HEAD(&hdr->list);
+ hdr->id = mpam_resctrl_pick_domain_id(id_cpu, comp);
+ hdr->rid = rid;
+ cpumask_copy(&hdr->cpu_mask, cpus);
+}
+
static void mpam_resctrl_domain_hdr_init(int cpu, struct mpam_component *comp,
enum resctrl_res_level rid,
struct rdt_domain_hdr *hdr)
{
lockdep_assert_cpus_held();
- INIT_LIST_HEAD(&hdr->list);
- hdr->id = mpam_resctrl_pick_domain_id(cpu, comp);
- hdr->rid = rid;
- cpumask_set_cpu(cpu, &hdr->cpu_mask);
+ mpam_resctrl_domain_hdr_init_mask(cpumask_of(cpu), cpu, comp, rid, hdr);
}
static void mpam_resctrl_online_domain_hdr(unsigned int cpu,
@@ -1805,12 +1816,12 @@ static void mpam_resctrl_online_domain_hdr(unsigned int cpu,
* indicating the parent structure can be freed.
* If there are other CPUs in the domain, returns false.
*/
-static bool mpam_resctrl_offline_domain_hdr(unsigned int cpu,
- struct rdt_domain_hdr *hdr)
+static bool mpam_resctrl_offline_domain_hdr_mask(const struct cpumask *cpus,
+ struct rdt_domain_hdr *hdr)
{
lockdep_assert_held(&domain_list_lock);
- cpumask_clear_cpu(cpu, &hdr->cpu_mask);
+ cpumask_andnot(&hdr->cpu_mask, &hdr->cpu_mask, cpus);
if (cpumask_empty(&hdr->cpu_mask)) {
list_del_rcu(&hdr->list);
synchronize_rcu();
@@ -1820,6 +1831,12 @@ static bool mpam_resctrl_offline_domain_hdr(unsigned int cpu,
return false;
}
+static bool mpam_resctrl_offline_domain_hdr(unsigned int cpu,
+ struct rdt_domain_hdr *hdr)
+{
+ return mpam_resctrl_offline_domain_hdr_mask(cpumask_of(cpu), hdr);
+}
+
static void mpam_resctrl_domain_insert(struct list_head *list,
struct rdt_domain_hdr *new)
{
@@ -1835,6 +1852,23 @@ static void mpam_resctrl_domain_insert(struct list_head *list,
list_add_tail_rcu(&new->list, pos);
}
+static struct mpam_component *find_component_nid(struct mpam_class *class, int nid)
+{
+ struct mpam_component *comp;
+
+ if (!class || class->type != MPAM_CLASS_MEMORY)
+ return NULL;
+
+ guard(srcu)(&mpam_srcu);
+ list_for_each_entry_srcu(comp, &class->components, class_list,
+ srcu_read_lock_held(&mpam_srcu)) {
+ if (comp->comp_id == nid)
+ return comp;
+ }
+
+ return NULL;
+}
+
static struct mpam_component *find_component(struct mpam_class *class, int cpu)
{
struct mpam_component *comp;
@@ -2094,6 +2128,297 @@ static void mpam_resctrl_offline_ctrls(unsigned int cpu, struct mpam_resctrl_res
}
}
+static bool mpam_mba_uses_memory_nid(void)
+{
+ struct mpam_resctrl_res *res = &mpam_resctrl_controls[RDT_RESOURCE_MBA];
+
+ return res->class && res->class->type == MPAM_CLASS_MEMORY;
+}
+
+static struct mpam_resctrl_dom *
+mpam_get_ctrl_domain_from_nid(int nid, struct mpam_resctrl_res *res,
+ struct resctrl_ctrl *ctrl)
+{
+ struct mpam_resctrl_dom *dom;
+
+ list_for_each_entry(dom, &ctrl->domains, resctrl_ctrl_dom.hdr.list) {
+ if (!dom->ctrl_comp || !dom->ctrl_comp->class)
+ continue;
+ if (dom->ctrl_comp->class->type != MPAM_CLASS_MEMORY)
+ continue;
+ if (dom->ctrl_comp->comp_id == nid)
+ return dom;
+ }
+
+ return NULL;
+}
+
+static struct mpam_resctrl_dom *
+mpam_get_mon_domain_from_nid(int nid, struct mpam_resctrl_res *res)
+{
+ struct mpam_resctrl_dom *dom;
+ struct rdt_resource *r = &res->resctrl_res;
+
+ if (!r->mon_capable)
+ return NULL;
+
+ list_for_each_entry(dom, &r->mon_domains, resctrl_mon_dom.hdr.list) {
+ if (!dom->ctrl_comp || !dom->ctrl_comp->class)
+ continue;
+ if (dom->ctrl_comp->class->type != MPAM_CLASS_MEMORY)
+ continue;
+ if (dom->ctrl_comp->comp_id == nid)
+ return dom;
+ }
+
+ return NULL;
+}
+
+static struct mpam_resctrl_dom *
+mpam_resctrl_alloc_ctrl_domain_nid(int nid, struct mpam_resctrl_res *res,
+ struct resctrl_ctrl *ctrl,
+ struct mpam_component *comp)
+{
+ int err;
+ struct mpam_resctrl_dom *dom;
+ struct rdt_ctrl_domain *ctrl_d;
+ struct rdt_resource *r = &res->resctrl_res;
+ int id_cpu = cpumask_first(cpu_possible_mask);
+
+ if (id_cpu >= nr_cpu_ids)
+ id_cpu = 0;
+
+ lockdep_assert_held(&domain_list_lock);
+
+ if (!r->alloc_capable)
+ return ERR_PTR(-EINVAL);
+
+ if (WARN_ON_ONCE(!comp))
+ return ERR_PTR(-EINVAL);
+
+ dom = kzalloc_node(sizeof(*dom), GFP_KERNEL, nid);
+ if (!dom)
+ return ERR_PTR(-ENOMEM);
+
+ dom->ctrl_comp = comp;
+
+ ctrl_d = &dom->resctrl_ctrl_dom;
+ mpam_resctrl_domain_hdr_init_mask(cpu_possible_mask, id_cpu, comp,
+ r->rid, &ctrl_d->hdr);
+ ctrl_d->hdr.type = RESCTRL_CTRL_DOMAIN;
+ err = resctrl_online_ctrl_domain(r, ctrl, ctrl_d);
+ if (err)
+ goto free_domain;
+
+ mpam_resctrl_domain_insert(&ctrl->domains, &ctrl_d->hdr);
+
+ return dom;
+
+free_domain:
+ kfree(dom);
+ return ERR_PTR(err);
+}
+
+static struct mpam_resctrl_dom *
+mpam_resctrl_alloc_mon_domain_nid(int nid, struct mpam_resctrl_res *res,
+ struct mpam_component *comp)
+{
+ int err;
+ struct mpam_resctrl_dom *dom;
+ struct rdt_l3_mon_domain *mon_d;
+ struct rdt_resource *r = &res->resctrl_res;
+ struct mpam_component *any_mon_comp = NULL;
+ struct mpam_resctrl_mon *mon;
+ enum resctrl_event_id eventid;
+ int id_cpu = cpumask_first(cpu_possible_mask);
+
+ if (id_cpu >= nr_cpu_ids)
+ id_cpu = 0;
+
+ lockdep_assert_held(&domain_list_lock);
+
+ if (!r->mon_capable)
+ return ERR_PTR(-EINVAL);
+
+ if (WARN_ON_ONCE(!comp))
+ return ERR_PTR(-EINVAL);
+
+ dom = kzalloc_node(sizeof(*dom), GFP_KERNEL, nid);
+ if (!dom)
+ return ERR_PTR(-ENOMEM);
+
+ dom->ctrl_comp = comp;
+
+ for_each_mpam_resctrl_mon(mon, eventid) {
+ struct mpam_component *mon_comp;
+
+ if (!mon->class)
+ continue;
+
+ mon_comp = find_component_nid(mon->class, nid);
+ dom->mon_comp[eventid] = mon_comp;
+ if (mon_comp)
+ any_mon_comp = mon_comp;
+ }
+ if (!any_mon_comp) {
+ err = -EFAULT;
+ goto free_domain;
+ }
+
+ mon_d = &dom->resctrl_mon_dom;
+ mpam_resctrl_domain_hdr_init_mask(cpu_possible_mask, id_cpu, any_mon_comp,
+ r->rid, &mon_d->hdr);
+ mon_d->hdr.type = RESCTRL_MON_DOMAIN;
+ err = resctrl_online_mon_domain(r, &mon_d->hdr);
+ if (err)
+ goto free_domain;
+
+ mpam_resctrl_domain_insert(&r->mon_domains, &mon_d->hdr);
+
+ return dom;
+
+free_domain:
+ kfree(dom);
+ return ERR_PTR(err);
+}
+
+static int mpam_resctrl_online_node(unsigned int nid)
+{
+ struct mpam_resctrl_res *res = &mpam_resctrl_controls[RDT_RESOURCE_MBA];
+ struct rdt_resource *r = &res->resctrl_res;
+ struct mpam_component *comp;
+ struct resctrl_ctrl *ctrl, *em_ctrl;
+ struct mpam_resctrl_dom *dom;
+
+ if (!res->class)
+ return 0;
+
+ comp = find_component_nid(res->class, nid);
+ if (!comp)
+ return 0;
+
+ guard(mutex)(&domain_list_lock);
+
+ if (r->alloc_capable) {
+ for_each_resource_ctrl(ctrl, r) {
+ dom = mpam_get_ctrl_domain_from_nid(nid, res, ctrl);
+ if (!dom) {
+ dom = mpam_resctrl_alloc_ctrl_domain_nid(nid, res,
+ ctrl, comp);
+ if (IS_ERR(dom))
+ return PTR_ERR(dom);
+ }
+
+ list_for_each_entry(em_ctrl, &ctrl->emulated_by, entry) {
+ dom = mpam_get_ctrl_domain_from_nid(nid, res, em_ctrl);
+ if (!dom) {
+ dom = mpam_resctrl_alloc_ctrl_domain_nid(nid, res,
+ em_ctrl, comp);
+ if (IS_ERR(dom))
+ return PTR_ERR(dom);
+ }
+ }
+ }
+ }
+
+ if (r->mon_capable) {
+ dom = mpam_get_mon_domain_from_nid(nid, res);
+ if (!dom) {
+ dom = mpam_resctrl_alloc_mon_domain_nid(nid, res, comp);
+ if (IS_ERR(dom))
+ return PTR_ERR(dom);
+ }
+ }
+
+ return 0;
+}
+
+static int mpam_resctrl_offline_node(unsigned int nid)
+{
+ struct mpam_resctrl_res *res = &mpam_resctrl_controls[RDT_RESOURCE_MBA];
+ struct rdt_resource *r = &res->resctrl_res;
+ struct resctrl_ctrl *ctrl, *em_ctrl;
+ struct mpam_resctrl_dom *dom;
+ struct rdt_ctrl_domain *ctrl_d;
+ struct rdt_l3_mon_domain *mon_d;
+
+ if (!res->class)
+ return 0;
+
+ if (!find_component_nid(res->class, nid))
+ return 0;
+
+ guard(mutex)(&domain_list_lock);
+
+ if (r->alloc_capable) {
+ for_each_resource_ctrl(ctrl, r) {
+ list_for_each_entry(em_ctrl, &ctrl->emulated_by, entry) {
+ dom = mpam_get_ctrl_domain_from_nid(nid, res, em_ctrl);
+ if (WARN_ON_ONCE(!dom))
+ continue;
+ ctrl_d = &dom->resctrl_ctrl_dom;
+ if (mpam_resctrl_offline_domain_hdr_mask(cpu_possible_mask,
+ &ctrl_d->hdr)) {
+ resctrl_offline_ctrl_domain(r, em_ctrl, ctrl_d);
+ kfree(dom);
+ }
+ }
+
+ dom = mpam_get_ctrl_domain_from_nid(nid, res, ctrl);
+ if (WARN_ON_ONCE(!dom))
+ continue;
+ ctrl_d = &dom->resctrl_ctrl_dom;
+ if (mpam_resctrl_offline_domain_hdr_mask(cpu_possible_mask,
+ &ctrl_d->hdr)) {
+ resctrl_offline_ctrl_domain(r, ctrl, ctrl_d);
+ kfree(dom);
+ }
+ }
+ }
+
+ if (r->mon_capable) {
+ dom = mpam_get_mon_domain_from_nid(nid, res);
+ if (WARN_ON_ONCE(!dom))
+ return 0;
+ mon_d = &dom->resctrl_mon_dom;
+ if (mpam_resctrl_offline_domain_hdr_mask(cpu_possible_mask, &mon_d->hdr)) {
+ resctrl_offline_mon_domain(r, &mon_d->hdr);
+ kfree(dom);
+ }
+ }
+
+ return 0;
+}
+
+static int mpam_resctrl_node_notifier(struct notifier_block *self,
+ unsigned long action, void *arg)
+{
+ struct node_notify *nn = arg;
+
+ if (nn->nid < 0 || !mpam_mba_uses_memory_nid())
+ return NOTIFY_OK;
+
+ /*
+ * Ignore nids that have CPUs. Resctrl needs to see the cpu offline
+ * call for each CPU to update the CPUs in control groups.
+ */
+ if (!cpumask_empty(cpumask_of_node(nn->nid)))
+ return NOTIFY_OK;
+
+ switch (action) {
+ case NODE_ADDED_FIRST_MEMORY:
+ mpam_resctrl_online_node(nn->nid);
+ break;
+ case NODE_REMOVED_LAST_MEMORY:
+ mpam_resctrl_offline_node(nn->nid);
+ break;
+ default:
+ break;
+ }
+
+ return NOTIFY_OK;
+}
+
int mpam_resctrl_online_cpu(unsigned int cpu)
{
struct mpam_resctrl_res *res;
@@ -2229,6 +2554,10 @@ int mpam_resctrl_setup(void)
}
}
+ if (mpam_mba_uses_memory_nid())
+ hotplug_node_notifier(mpam_resctrl_node_notifier,
+ RESCTRL_CALLBACK_PRI);
+
cpus_read_unlock();
if (!resctrl_arch_alloc_capable() && !resctrl_arch_mon_capable()) {
diff --git a/include/linux/memory.h b/include/linux/memory.h
index 463dc02f6cff..1497d4be38ee 100644
--- a/include/linux/memory.h
+++ b/include/linux/memory.h
@@ -120,6 +120,7 @@ struct mem_section;
#define CPUSET_CALLBACK_PRI 10
#define MEMTIER_HOTPLUG_PRI 100
#define KSM_CALLBACK_PRI 100
+#define RESCTRL_CALLBACK_PRI 100
#ifndef CONFIG_MEMORY_HOTPLUG
static inline void memory_dev_init(void)
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [PATCH RFC v2 13/19] resctrl: Add mbm_assign_scope_mode for native assignment file names
2026-08-31 17:22 [PATCH RFC v2 00/19] arm,fs/resctrl: ARM MPAM MB_NODE support Fenghua Yu
` (11 preceding siblings ...)
2026-08-31 17:22 ` [PATCH RFC v2 12/19] arm_mpam: resctrl: Add NUMA node notifier for domain online/offline Fenghua Yu
@ 2026-08-31 17:22 ` Fenghua Yu
2026-08-31 17:22 ` [PATCH RFC v2 14/19] Documentation: resctrl: document mbm_assign_scope_mode Fenghua Yu
` (6 subsequent siblings)
19 siblings, 0 replies; 21+ messages in thread
From: Fenghua Yu @ 2026-08-31 17:22 UTC (permalink / raw)
To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
Will Deacon, Catalin Marinas, Shaopeng Tan, Chen Yu, Babu Moger,
Drew Fustini, Vikram Sethi, Shanker Donthineni, Newton Liu,
Richard Cheng
Cc: linux-kernel, linux-arm-kernel, Fenghua Yu
On platforms whose MBM counters are backed by NUMA node MSCs,
mbm_NODE_assignments is the native assignment file. User space that
already programs mbm_L3_assignments needs a compatible default.
Add info/MB_MON/mbm_assign_scope_mode. Legacy (default) always exposes
mbm_L3_assignments; that file still programs node-scoped counters when
node MSCs provide MBM. Native mode replaces it with
mbm_NODE_assignments on those platforms.
Signed-off-by: Fenghua Yu <fenghuay@nvidia.com>
---
fs/resctrl/internal.h | 9 +++
fs/resctrl/monitor.c | 147 +++++++++++++++++++++++++++++++++++++---
fs/resctrl/rdtgroup.c | 9 +++
include/linux/resctrl.h | 6 ++
4 files changed, 162 insertions(+), 9 deletions(-)
diff --git a/fs/resctrl/internal.h b/fs/resctrl/internal.h
index 32c7d53790ec..f46d4fb7c624 100644
--- a/fs/resctrl/internal.h
+++ b/fs/resctrl/internal.h
@@ -457,6 +457,15 @@ int mbm_L3_assignments_show(struct kernfs_open_file *of, struct seq_file *s, voi
ssize_t mbm_L3_assignments_write(struct kernfs_open_file *of, char *buf, size_t nbytes,
loff_t off);
+
+int resctrl_mbm_assign_scope_mode_show(struct kernfs_open_file *of, struct seq_file *s,
+ void *v);
+
+ssize_t resctrl_mbm_assign_scope_mode_write(struct kernfs_open_file *of, char *buf,
+ size_t nbytes, loff_t off);
+
+void resctrl_mbm_assign_files_apply(struct kernfs_node *kn);
+
int resctrl_io_alloc_show(struct kernfs_open_file *of, struct seq_file *seq, void *v);
int rdtgroup_init_cat(struct rdt_resource_final *f, struct resctrl_ctrl *ctrl,
diff --git a/fs/resctrl/monitor.c b/fs/resctrl/monitor.c
index d555f2d19164..074dc2a040dc 100644
--- a/fs/resctrl/monitor.c
+++ b/fs/resctrl/monitor.c
@@ -18,6 +18,7 @@
#define pr_fmt(fmt) "resctrl: " fmt
#include <linux/cpu.h>
+#include <linux/kernfs.h>
#include <linux/resctrl.h>
#include <linux/sizes.h>
#include <linux/slab.h>
@@ -1704,6 +1705,130 @@ int resctrl_available_mbm_cntrs_show(struct kernfs_open_file *of,
return ret;
}
+static bool resctrl_mbm_has_node_mscs(void)
+{
+ struct rdt_resource *r = resctrl_arch_get_resource(RDT_RESOURCE_MBA);
+
+ return r->mon_capable && r->mon_scope == RESCTRL_NODE;
+}
+
+static bool resctrl_mbm_show_l3_assignments(void)
+{
+ return !resctrl_arch_get_resource(RDT_RESOURCE_MBA)->mon.mbm_assign_scope_native ||
+ !resctrl_mbm_has_node_mscs();
+}
+
+static bool resctrl_mbm_show_node_assignments(void)
+{
+ return resctrl_arch_get_resource(RDT_RESOURCE_MBA)->mon.mbm_assign_scope_native &&
+ resctrl_mbm_has_node_mscs();
+}
+
+static struct rdt_resource *mbm_l3_assignments_resource(void)
+{
+ if (resctrl_mbm_has_node_mscs())
+ return resctrl_arch_get_resource(RDT_RESOURCE_MBA);
+
+ return resctrl_arch_get_resource(RDT_RESOURCE_L3);
+}
+
+static void resctrl_mbm_assign_file_show(struct kernfs_node *parent, const char *name,
+ bool show)
+{
+ struct kernfs_node *kn;
+
+ kn = kernfs_find_and_get(parent, name);
+ if (!kn)
+ return;
+
+ kernfs_show(kn, show);
+ kernfs_put(kn);
+}
+
+void resctrl_mbm_assign_files_apply(struct kernfs_node *kn)
+{
+ if (!kn)
+ return;
+
+ resctrl_mbm_assign_file_show(kn, "mbm_L3_assignments",
+ resctrl_mbm_show_l3_assignments());
+ resctrl_mbm_assign_file_show(kn, "mbm_NODE_assignments",
+ resctrl_mbm_show_node_assignments());
+}
+
+static void resctrl_mbm_assign_files_apply_all(void)
+{
+ struct rdtgroup *prgrp, *crgrp;
+
+ lockdep_assert_held(&rdtgroup_mutex);
+
+ list_for_each_entry(prgrp, &rdt_all_groups, rdtgroup_list) {
+ resctrl_mbm_assign_files_apply(prgrp->kn);
+ list_for_each_entry(crgrp, &prgrp->mon.crdtgrp_list, mon.crdtgrp_list)
+ resctrl_mbm_assign_files_apply(crgrp->kn);
+ }
+}
+
+int resctrl_mbm_assign_scope_mode_show(struct kernfs_open_file *of,
+ struct seq_file *s, void *v)
+{
+ struct rdt_resource *r = rdt_kn_parent_priv(of->kn);
+
+ if (!info_kn_lock(of->kn))
+ return -ENOENT;
+
+ if (r->mon.mbm_assign_scope_native)
+ seq_puts(s, "legacy [native]\n");
+ else
+ seq_puts(s, "[legacy] native\n");
+
+ info_kn_unlock(of->kn);
+
+ return 0;
+}
+
+ssize_t resctrl_mbm_assign_scope_mode_write(struct kernfs_open_file *of, char *buf,
+ size_t nbytes, loff_t off)
+{
+ struct rdt_resource *r = rdt_kn_parent_priv(of->kn);
+ bool native;
+ int ret = 0;
+
+ if (!info_kn_lock(of->kn))
+ return -ENOENT;
+
+ rdt_last_cmd_clear();
+
+ /* Valid input requires a trailing newline */
+ if (nbytes == 0 || buf[nbytes - 1] != '\n') {
+ rdt_last_cmd_puts("mbm_assign_scope_mode: Invalid input\n");
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
+ buf[nbytes - 1] = '\0';
+
+ if (!strcmp(buf, "legacy")) {
+ native = false;
+ } else if (!strcmp(buf, "native")) {
+ native = true;
+ } else {
+ rdt_last_cmd_puts("Unsupported assign scope mode\n");
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
+ if (r->mon.mbm_assign_scope_native != native) {
+ r->mon.mbm_assign_scope_native = native;
+ resctrl_mbm_assign_files_apply_all();
+ }
+
+out_unlock:
+ info_kn_unlock(of->kn);
+
+ return ret ?: nbytes;
+}
+
static int mbm_assignments_show(struct kernfs_open_file *of, struct seq_file *s,
void *v, struct rdt_resource *r)
{
@@ -1753,8 +1878,7 @@ static int mbm_assignments_show(struct kernfs_open_file *of, struct seq_file *s,
int mbm_L3_assignments_show(struct kernfs_open_file *of, struct seq_file *s, void *v)
{
- return mbm_assignments_show(of, s, v,
- resctrl_arch_get_resource(RDT_RESOURCE_L3));
+ return mbm_assignments_show(of, s, v, mbm_l3_assignments_resource());
}
int mbm_NODE_assignments_show(struct kernfs_open_file *of, struct seq_file *s, void *v)
@@ -1873,7 +1997,7 @@ static ssize_t mbm_assignments_write(struct kernfs_open_file *of, char *buf,
/* Valid input requires a trailing newline */
if (nbytes == 0 || buf[nbytes - 1] != '\n') {
- rdt_last_cmd_puts("mbm_L3_assignments: Invalid input\n");
+ rdt_last_cmd_puts("mbm assignments: Invalid input\n");
ret = -EINVAL;
goto out_unlock;
}
@@ -1909,14 +2033,14 @@ ssize_t mbm_L3_assignments_write(struct kernfs_open_file *of, char *buf,
size_t nbytes, loff_t off)
{
return mbm_assignments_write(of, buf, nbytes, off,
- resctrl_arch_get_resource(RDT_RESOURCE_L3));
+ mbm_l3_assignments_resource());
}
ssize_t mbm_NODE_assignments_write(struct kernfs_open_file *of, char *buf,
size_t nbytes, loff_t off)
{
return mbm_assignments_write(of, buf, nbytes, off,
- resctrl_arch_get_resource(RDT_RESOURCE_MBA));
+ resctrl_arch_get_resource(RDT_RESOURCE_MBA));
}
static int closid_num_dirty_rmid_alloc(struct rdt_resource *r)
@@ -2012,10 +2136,15 @@ static void resctrl_mon_resource_init(struct rdt_resource *r)
resctrl_file_mode_init("event_filter", 0644);
resctrl_file_fflags_init("mbm_assign_on_mkdir", RFTYPE_MON_INFO |
fflags);
- if (r->rid == RDT_RESOURCE_MBA)
- resctrl_file_fflags_init("mbm_NODE_assignments", RFTYPE_MON_BASE);
- else
- resctrl_file_fflags_init("mbm_L3_assignments", RFTYPE_MON_BASE);
+ resctrl_file_fflags_init("mbm_L3_assignments", RFTYPE_MON_BASE);
+ if (r->rid == RDT_RESOURCE_MBA) {
+ r->mon.mbm_assign_scope_native = false;
+ resctrl_file_fflags_init("mbm_assign_scope_mode",
+ RFTYPE_MON_INFO | RFTYPE_RES_MB);
+ if (r->mon_scope == RESCTRL_NODE)
+ resctrl_file_fflags_init("mbm_NODE_assignments",
+ RFTYPE_MON_BASE);
+ }
resctrl_file_fflags_init("mbm_assign_mode", RFTYPE_MON_INFO |
fflags);
}
diff --git a/fs/resctrl/rdtgroup.c b/fs/resctrl/rdtgroup.c
index a16e35b587c4..1547306dc8d6 100644
--- a/fs/resctrl/rdtgroup.c
+++ b/fs/resctrl/rdtgroup.c
@@ -2316,6 +2316,13 @@ static struct rftype res_common_files[] = {
.seq_show = mbm_NODE_assignments_show,
.write = mbm_NODE_assignments_write,
},
+ {
+ .name = "mbm_assign_scope_mode",
+ .mode = 0644,
+ .kf_ops = &rdtgroup_kf_single_ops,
+ .seq_show = resctrl_mbm_assign_scope_mode_show,
+ .write = resctrl_mbm_assign_scope_mode_write,
+ },
{
.name = "mbm_assign_mode",
.mode = 0644,
@@ -2653,6 +2660,8 @@ static int rdtgroup_add_files(struct kernfs_node *kn, unsigned long fflags)
}
}
+ resctrl_mbm_assign_files_apply(kn);
+
return 0;
error:
pr_warn("Failed to add %s, err=%d\n", rft->name, ret);
diff --git a/include/linux/resctrl.h b/include/linux/resctrl.h
index b8df7489539f..141e2ff594f9 100644
--- a/include/linux/resctrl.h
+++ b/include/linux/resctrl.h
@@ -380,6 +380,11 @@ enum resctrl_ctrl_type {
* events of monitor groups created via mkdir.
* @mbm_cntr_configurable: True if assignable counters are configurable.
* @mbm_cntr_assign_fixed: True if the counter assignment mode is fixed.
+ * @mbm_assign_scope_native: True if assignment files use native names.
+ * Legacy mode always exposes "mbm_L3_assignments".
+ * Native mode exposes "mbm_NODE_assignments" when
+ * NUMA node MSCs are present, otherwise
+ * "mbm_L3_assignments".
*/
struct resctrl_mon {
u32 num_rmid;
@@ -389,6 +394,7 @@ struct resctrl_mon {
bool mbm_assign_on_mkdir;
bool mbm_cntr_configurable;
bool mbm_cntr_assign_fixed;
+ bool mbm_assign_scope_native;
};
/**
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [PATCH RFC v2 14/19] Documentation: resctrl: document mbm_assign_scope_mode
2026-08-31 17:22 [PATCH RFC v2 00/19] arm,fs/resctrl: ARM MPAM MB_NODE support Fenghua Yu
` (12 preceding siblings ...)
2026-08-31 17:22 ` [PATCH RFC v2 13/19] resctrl: Add mbm_assign_scope_mode for native assignment file names Fenghua Yu
@ 2026-08-31 17:22 ` Fenghua Yu
2026-08-31 17:22 ` [PATCH RFC v2 15/19] Documentation: arm64: mpam: document memory-level MB control and NUMA nodes Fenghua Yu
` (5 subsequent siblings)
19 siblings, 0 replies; 21+ messages in thread
From: Fenghua Yu @ 2026-08-31 17:22 UTC (permalink / raw)
To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
Will Deacon, Catalin Marinas, Shaopeng Tan, Chen Yu, Babu Moger,
Drew Fustini, Vikram Sethi, Shanker Donthineni, Newton Liu,
Richard Cheng
Cc: linux-kernel, linux-arm-kernel, Fenghua Yu
Document info/MB_MON/mbm_assign_scope_mode and the legacy
mbm_L3_assignments vs native mbm_NODE_assignments names.
Signed-off-by: Fenghua Yu <fenghuay@nvidia.com>
---
Documentation/filesystems/resctrl.rst | 44 ++++++++++++++++++++++++---
1 file changed, 40 insertions(+), 4 deletions(-)
diff --git a/Documentation/filesystems/resctrl.rst b/Documentation/filesystems/resctrl.rst
index 434e6f42db4a..5dac9aa1da01 100644
--- a/Documentation/filesystems/resctrl.rst
+++ b/Documentation/filesystems/resctrl.rst
@@ -550,8 +550,10 @@ with the following files:
the user. Each event within a resctrl group can be assigned independently.
In this mode, a monitoring event can only accumulate data while it is backed
- by a hardware counter. Use "mbm_L3_assignments" found in each CTRL_MON and MON
- group to specify which of the events should have a counter assigned. The number
+ by a hardware counter. Use the assignment file found in each CTRL_MON and MON
+ group ("mbm_L3_assignments", or "mbm_NODE_assignments" in native mode when
+ NUMA node MSCs provide MBM) to specify which of the events should have a
+ counter assigned. The number
of counters available is described in the "num_mbm_cntrs" file. Changing the
mode may cause all counters on the resource to reset.
@@ -583,6 +585,37 @@ with the following files:
# echo "default" > /sys/fs/resctrl/info/L3_MON/mbm_assign_mode
+"mbm_assign_scope_mode":
+ Exists in info/MB_MON when memory-bandwidth monitoring supports
+ assignable counters. Selects the name of the per-group assignment
+ file. The enclosed brackets indicate which mode is enabled. The
+ default is legacy.
+
+ ::
+
+ # cat /sys/fs/resctrl/info/MB_MON/mbm_assign_scope_mode
+ [legacy] native
+
+ "legacy":
+ Always exposes "mbm_L3_assignments" in CTRL_MON and MON
+ groups. On platforms whose MBM counters are backed by NUMA
+ node MSCs the file still programs those node-scoped counters;
+ only the name is kept for compatibility.
+
+ "native":
+ Exposes "mbm_NODE_assignments" when NUMA node MSCs provide
+ MBM, otherwise "mbm_L3_assignments".
+
+ * To enable native assignment file names:
+ ::
+
+ # echo "native" > /sys/fs/resctrl/info/MB_MON/mbm_assign_scope_mode
+
+ * To restore the legacy name:
+ ::
+
+ # echo "legacy" > /sys/fs/resctrl/info/MB_MON/mbm_assign_scope_mode
+
"num_mbm_cntrs":
The maximum number of counters (total of available and assigned counters) in
each domain when the system supports mbm_event mode.
@@ -859,9 +892,12 @@ When monitoring is enabled all MON groups will also contain:
When monitoring is enabled all MON groups may also contain:
-"mbm_L3_assignments":
+"mbm_L3_assignments" / "mbm_NODE_assignments":
Exists when "mbm_event" counter assignment mode is supported and lists the
- counter assignment states of the group.
+ counter assignment states of the group. "mbm_L3_assignments" is the
+ legacy name and the default. Writing "native" to
+ info/MB_MON/mbm_assign_scope_mode replaces it with "mbm_NODE_assignments"
+ when MBM is backed by NUMA node MSCs. The file format is the same.
The assignment list is displayed in the following format:
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [PATCH RFC v2 15/19] Documentation: arm64: mpam: document memory-level MB control and NUMA nodes
2026-08-31 17:22 [PATCH RFC v2 00/19] arm,fs/resctrl: ARM MPAM MB_NODE support Fenghua Yu
` (13 preceding siblings ...)
2026-08-31 17:22 ` [PATCH RFC v2 14/19] Documentation: resctrl: document mbm_assign_scope_mode Fenghua Yu
@ 2026-08-31 17:22 ` Fenghua Yu
2026-08-31 17:22 ` [PATCH RFC v2 16/19] Documentation: resctrl: document NODE-scoped MBA domains and mon_NODE monitoring Fenghua Yu
` (4 subsequent siblings)
19 siblings, 0 replies; 21+ messages in thread
From: Fenghua Yu @ 2026-08-31 17:22 UTC (permalink / raw)
To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
Will Deacon, Catalin Marinas, Shaopeng Tan, Chen Yu, Babu Moger,
Drew Fustini, Vikram Sethi, Shanker Donthineni, Newton Liu,
Richard Cheng
Cc: linux-kernel, linux-arm-kernel, Fenghua Yu
The MB control can be backed by a memory-level MSC above L3 rather than
the L3 cache MSC. In that case resctrl identifies domains by NUMA node id
instead of L3 cache id, exposes a node-scoped MB_NODE control, and can
manage CPU-less memory nodes by falling back to cpu_possible_mask.
Update the arm64 MPAM documentation to describe the L3-cache and memory
MSC paths for both the MB control and MBWU monitoring, how to tell them
apart via the control "scope" file, how NUMA node ids map to
/sys/devices/system/node/node*/, and CPU-less NUMA node handling.
Signed-off-by: Fenghua Yu <fenghuay@nvidia.com>
---
Documentation/arch/arm64/mpam.rst | 105 +++++++++++++++++++-----------
1 file changed, 68 insertions(+), 37 deletions(-)
diff --git a/Documentation/arch/arm64/mpam.rst b/Documentation/arch/arm64/mpam.rst
index 67fe515ed501..88ddee8705e4 100644
--- a/Documentation/arch/arm64/mpam.rst
+++ b/Documentation/arch/arm64/mpam.rst
@@ -44,48 +44,79 @@ The supported features are:
placement.
* Memory bandwidth maximum controls (MBW_MAX) on or after the L3 cache.
- resctrl uses the L3 cache-id to identify where the memory bandwidth
- control is applied. For this reason the platform must have an L3 cache
- with cache-id's supplied by firmware. (It doesn't need to support MPAM.)
-
- To be exported as the 'MB' schema, the topology of the group of MSC chosen
- must match the topology of the L3 cache so that the cache-id's can be
- repainted. For example: Platforms with Memory bandwidth maximum controls
- on CPU-less NUMA nodes cannot expose the 'MB' schema to resctrl as these
- nodes do not have a corresponding L3 cache. If the memory bandwidth
- control is on the memory rather than the L3 then there must be a single
- global L3 as otherwise it is unknown which L3 the traffic came from. There
- must be no caches between the L3 and the memory so that the two ends of
- the path have equivalent traffic.
-
- When the MPAM driver finds multiple groups of MSC it can use for the 'MB'
- schema, it prefers the group closest to the L3 cache.
+ resctrl exposes these as the ``MB`` resource. The domain identifiers
+ used in the ``MB:`` schemata line depend on which MSC group backs the
+ resource:
+
+ **L3-cache MSC (cache-level MB control).**
+ When the MB control hardware sits on the L3 cache MSC, resctrl uses L3
+ cache-ids to identify where bandwidth is applied. The topology of the
+ MSC group must match the L3 cache topology so that cache-ids can be
+ repainted. If the memory bandwidth control is on the memory rather
+ than the L3 then there must be a single global L3 as otherwise it is
+ unknown which L3 the traffic came from. There must be no caches
+ between the L3 and the memory so that the two ends of the path have
+ equivalent traffic.
+
+ **Memory MSC (memory-level MB control).**
+ When the MB control hardware sits on a memory MSC above L3, resctrl uses
+ NUMA node identifiers instead of L3 cache-ids. The native node-scoped
+ control is exposed as ``MB_NODE``; the legacy ``MB`` control keeps the
+ ``MB:`` schemata line for backward compatibility and, in the default
+ legacy mode, is emulated by ``MB_NODE`` when it has no hardware of its
+ own. See the MB control emulation section in
+ Documentation/filesystems/resctrl.rst for the control layout and
+ emulation modes.
+
+ On either path, read the control ``scope`` file under
+ ``info/MB/resource_schemata/`` (``L3`` or ``NODE``) to learn which
+ identifier space the ``MB:`` line uses.
+
+ When ``scope`` reads ``NODE``, each numeric ID ``XX`` in the ``MB:``
+ line is a NUMA node id and corresponds to the standard NUMA node
+ directory ``/sys/devices/system/node/nodeXX/``. That directory
+ describes the node: ``cpulist``/``cpumap`` (the CPUs local to it, empty
+ for a CPU-less node), ``distance`` (NUMA distances to other nodes),
+ ``meminfo`` (its memory), and its ``memoryX`` symlinks. Use these
+ files to map an ``MB:`` (or ``MB_NODE:``) entry to a physical NUMA node
+ and its CPUs and memory.
+
+ **CPU-less NUMA nodes.**
+ A memory MSC may be associated with a NUMA node that has no local CPUs
+ (for example a memory-only node that still participates in bandwidth
+ control). The driver falls back to ``cpu_possible_mask`` for the MSC
+ affinity so that traffic from remote CPUs is still accounted for and
+ the node can appear as an ``MB``/``MB_NODE`` domain.
+
+ When the MPAM driver finds multiple groups of MSC it can use for the
+ ``MB`` resource, it prefers the group closest to the L3 cache.
* Cache Storage Usage (CSU) counters can expose the 'llc_occupancy' provided
there is at least one CSU monitor on each MSC that makes up the L3 group.
Exposing CSU counters from other caches or devices is not supported.
-* Memory Bandwidth Usage (MBWU) on or after the L3 cache. resctrl uses the
- L3 cache-id to identify where the memory bandwidth is measured. For this
- reason the platform must have an L3 cache with cache-id's supplied by
- firmware. (The platform doesn't need to support MPAM.)
-
- Memory bandwidth monitoring makes use of MBWU monitors in each MSC that
- makes up the L3 group. If the memory bandwidth monitoring is on the memory
- rather than the L3 then there must be a single global L3 as otherwise it
- is unknown which L3 the traffic came from.
-
- To expose 'mbm_total_bytes', the topology of the group of MSC chosen must
- match the topology of the L3 cache so that the cache-id's can be
- repainted. For example: Platforms with Memory bandwidth monitors on
- CPU-less NUMA nodes cannot expose 'mbm_total_bytes' as these nodes do not
- have a corresponding L3 cache. 'mbm_local_bytes' is not exposed as MPAM
- cannot distinguish local traffic from global traffic.
-
- All these restrictions based on L3 cache are due to resctrl, currently, only
- supporting monitoring at the L3 scope. It is expected that going forward more
- MBWU monitors can be exposed to the user after support for more monitoring
- scopes is added to resctrl.
+* Memory Bandwidth Usage (MBWU) on or after the L3 cache. resctrl can
+ expose ``mbm_total_bytes`` from either an L3-cache MSC or a memory MSC:
+
+ **L3-cache MSC.**
+ When MBWU monitors sit on the L3 cache MSC, counters are exposed on the
+ ``L3_MON`` resource and use L3 cache-ids. The MSC group topology must
+ match the L3 cache topology so that cache-ids can be repainted.
+
+ **Memory MSC.**
+ When MBWU monitors sit on a memory MSC above L3, counters are exposed on
+ the ``MB`` resource with node scope. Monitor directories are named
+ ``mon_NODE_XX`` where ``XX`` is the same NUMA node identifier that appears
+ in the ``MB:`` schemata line. CPU-less memory nodes are supported the
+ same way as for MB controls above.
+
+ ``mbm_local_bytes`` is not exposed as MPAM cannot distinguish local
+ traffic from global traffic on these paths.
+
+ When only L3-scoped monitoring was supported, platforms with memory
+ bandwidth monitors on CPU-less NUMA nodes could not expose
+ ``mbm_total_bytes``. Node-scoped monitoring on the ``MB`` resource
+ removes that restriction for memory-class MSCs.
Reporting Bugs
==============
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [PATCH RFC v2 16/19] Documentation: resctrl: document NODE-scoped MBA domains and mon_NODE monitoring
2026-08-31 17:22 [PATCH RFC v2 00/19] arm,fs/resctrl: ARM MPAM MB_NODE support Fenghua Yu
` (14 preceding siblings ...)
2026-08-31 17:22 ` [PATCH RFC v2 15/19] Documentation: arm64: mpam: document memory-level MB control and NUMA nodes Fenghua Yu
@ 2026-08-31 17:22 ` Fenghua Yu
2026-08-31 17:22 ` [PATCH RFC v2 17/19] Documentation: resctrl: document MB_NODE emulation example on ARM MPAM Fenghua Yu
` (3 subsequent siblings)
19 siblings, 0 replies; 21+ messages in thread
From: Fenghua Yu @ 2026-08-31 17:22 UTC (permalink / raw)
To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
Will Deacon, Catalin Marinas, Shaopeng Tan, Chen Yu, Babu Moger,
Drew Fustini, Vikram Sethi, Shanker Donthineni, Newton Liu,
Richard Cheng
Cc: linux-kernel, linux-arm-kernel, Fenghua Yu
On ARM MPAM platforms backed by a memory-level MSC, the MB control's
scope reads NODE and schemata identifiers are NUMA node ids rather than
L3 cache ids. Document the scope file values, mon_NODE_XX monitor
directories, and how to interpret MB: lines for both L3-scoped and
NODE-scoped MBA resources in Documentation/filesystems/resctrl.rst.
Signed-off-by: Fenghua Yu <fenghuay@nvidia.com>
---
Documentation/filesystems/resctrl.rst | 40 ++++++++++++++++++++++++---
1 file changed, 36 insertions(+), 4 deletions(-)
diff --git a/Documentation/filesystems/resctrl.rst b/Documentation/filesystems/resctrl.rst
index 5dac9aa1da01..fbce6a1bd4c4 100644
--- a/Documentation/filesystems/resctrl.rst
+++ b/Documentation/filesystems/resctrl.rst
@@ -285,6 +285,11 @@ properties. The name of the control is used in the "schemata" where user space
can view and modify control settings or portions allocated to the control group
as the control's properties allow.
+Numeric identifiers in a control's schemata line follow the control's
+scope. ``L3`` means the IDs are L3 cache IDs; ``NODE`` means they are
+NUMA node IDs. The same identifier space is used by the matching
+``mon_L3_XX`` or ``mon_NODE_XX`` directories under ``mon_data``.
+
For example, below shows MB, MB_MIN, and MB_MAX as controls for the "MB" resource.
info/
@@ -847,6 +852,14 @@ When monitoring is enabled all MON groups will also contain:
each instance of an L3 cache. Each directory contains files for the enabled
L3 events (e.g. "llc_occupancy", "mbm_total_bytes", and "mbm_local_bytes").
+ On platforms where memory bandwidth monitoring is associated with the
+ MB resource (for example ARM MPAM systems with a memory-level MSC),
+ there will be a "mon_NODE_XX" directory for each MB monitor domain.
+ "XX" is the NUMA node id that also appears in the "MB:" line of
+ "schemata" when that control is node-scoped. Each "mon_NODE_XX"
+ directory contains the MBM events enabled for that resource (for
+ example "mbm_total_bytes").
+
If telemetry monitoring is enabled, there will be a "mon_PERF_PKG_YY"
directory for each physical processor package. Each directory contains
files for the enabled telemetry events (e.g. "core_energy". "activity",
@@ -1183,19 +1196,38 @@ or
Memory bandwidth Allocation (default mode)
------------------------------------------
-Memory b/w domain is L3 cache.
-::
+On most platforms the memory bandwidth (MBA) domain is the L3 cache and
+the numeric identifiers in the "MB:" line are L3 cache ids::
MB:<cache_id0>=bandwidth0;<cache_id1>=bandwidth1;...
+On ARM MPAM platforms backed by a memory-level MSC, the MB control is
+node-scoped and the identifiers are NUMA node ids instead::
+
+ MB:<node_id0>=bandwidth0;<node_id1>=bandwidth1;...
+
+Example on a system where MB domains are NUMA nodes::
+
+ # cat schemata
+ MB:0=100;1=100;2=100;10=100
+ L3:1=ffff;2=ffff
+
+The same NUMA node ids appear as "mon_NODE_XX" directories under
+"mon_data". See "Resource controls" for how domain identifiers follow
+control scope, and "Resource control mode" for how a native node-scoped
+control can emulate the legacy ``MB:`` line.
+
Memory bandwidth Allocation specified in MiBps
----------------------------------------------
-Memory bandwidth domain is L3 cache.
-::
+When MBA domains are L3 caches::
MB:<cache_id0>=bw_MiBps0;<cache_id1>=bw_MiBps1;...
+When the MB control is node-scoped::
+
+ MB:<node_id0>=bw_MiBps0;<node_id1>=bw_MiBps1;...
+
Slow Memory Bandwidth Allocation (SMBA)
---------------------------------------
AMD hardware supports Slow Memory Bandwidth Allocation (SMBA).
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [PATCH RFC v2 17/19] Documentation: resctrl: document MB_NODE emulation example on ARM MPAM
2026-08-31 17:22 [PATCH RFC v2 00/19] arm,fs/resctrl: ARM MPAM MB_NODE support Fenghua Yu
` (15 preceding siblings ...)
2026-08-31 17:22 ` [PATCH RFC v2 16/19] Documentation: resctrl: document NODE-scoped MBA domains and mon_NODE monitoring Fenghua Yu
@ 2026-08-31 17:22 ` Fenghua Yu
2026-08-31 17:22 ` [PATCH RFC v2 18/19] arm_mpam: Add KUnit test for CPU-less NUMA node affinity Fenghua Yu
` (2 subsequent siblings)
19 siblings, 0 replies; 21+ messages in thread
From: Fenghua Yu @ 2026-08-31 17:22 UTC (permalink / raw)
To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
Will Deacon, Catalin Marinas, Shaopeng Tan, Chen Yu, Babu Moger,
Drew Fustini, Vikram Sethi, Shanker Donthineni, Newton Liu,
Richard Cheng
Cc: linux-kernel, linux-arm-kernel, Fenghua Yu
Add an ARM MPAM example to "Resource control mode": MB_NODE backs MB
when the MBA resource is a memory-level MSC and L3 has no MBA hardware.
Document the info/MB/schemata/ nesting, control_mode, and the legacy
MB: versus native MB_NODE: schemata lines.
Signed-off-by: Fenghua Yu <fenghuay@nvidia.com>
---
Documentation/filesystems/resctrl.rst | 51 +++++++++++++++++++++++++++
1 file changed, 51 insertions(+)
diff --git a/Documentation/filesystems/resctrl.rst b/Documentation/filesystems/resctrl.rst
index fbce6a1bd4c4..450cf269472d 100644
--- a/Documentation/filesystems/resctrl.rst
+++ b/Documentation/filesystems/resctrl.rst
@@ -353,6 +353,57 @@ schemata file::
MB_MIN:0=1;1=1
MB_MAX:0=1000;1=1000
+ARM MPAM example
+~~~~~~~~~~~~~~~~
+On ARM MPAM systems where the MBA resource is backed by a memory-level
+MSC and the L3 cache MSC has no usable MBA hardware, the kernel exposes
+two bandwidth controls. ``MB_NODE`` backs ``MB`` so legacy mode keeps a
+working ``MB:`` schemata line.
+
+``MB`` (default):
+ The legacy control. It has no bandwidth hardware of its own, so
+ in ``legacy`` ``control_mode`` writes to the ``MB:`` line are
+ applied through ``MB_NODE``. Domain identifiers are NUMA node
+ ids.
+
+``MB_NODE``:
+ The native, node-scoped control backed by memory-level MBW
+ hardware. The ``MB_NODE:`` schemata line uses the same NUMA node
+ identifiers. The control directory is nested under ``MB`` in
+ ``info/MB/schemata/``.
+
+``MB_NODE`` is created only in that configuration. If the L3 cache MSC
+already has usable MBA hardware, ``MB`` is backed by that cache-level
+hardware and ``MB_NODE`` is not created.
+
+``control_mode`` lives in ``info/MB/``. Switching mode changes which
+lines appear in each group's ``schemata`` file; it does not rebuild the
+``info/MB/schemata/`` tree.
+
+**Memory-level MBA, no L3 MBA** (``MB`` emulated by ``MB_NODE``)::
+
+ info/MB/
+ ├── control_mode
+ └── schemata/
+ └── MB/
+ └── MB_NODE/
+
+In ``legacy`` mode (the default) the group's schemata file shows the
+emulated ``MB:`` line::
+
+ # cat /sys/fs/resctrl/info/MB/control_mode
+ [legacy] native
+ # cat schemata
+ MB:0=100;1=100
+
+In ``native`` mode the ``MB:`` line is replaced by ``MB_NODE:``::
+
+ # echo native > /sys/fs/resctrl/info/MB/control_mode
+ # cat /sys/fs/resctrl/info/MB/control_mode
+ legacy [native]
+ # cat schemata
+ MB_NODE:0=100;1=100
+
Resource control properties
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Every control directory contains a file named "type" that specifies which
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [PATCH RFC v2 18/19] arm_mpam: Add KUnit test for CPU-less NUMA node affinity
2026-08-31 17:22 [PATCH RFC v2 00/19] arm,fs/resctrl: ARM MPAM MB_NODE support Fenghua Yu
` (16 preceding siblings ...)
2026-08-31 17:22 ` [PATCH RFC v2 17/19] Documentation: resctrl: document MB_NODE emulation example on ARM MPAM Fenghua Yu
@ 2026-08-31 17:22 ` Fenghua Yu
2026-08-31 17:22 ` [PATCH RFC v2 19/19] selftests/resctrl: Add MB emulation test for ARM MPAM Fenghua Yu
2026-09-01 9:37 ` [PATCH RFC v2 00/19] arm,fs/resctrl: ARM MPAM MB_NODE support Richard Cheng
19 siblings, 0 replies; 21+ messages in thread
From: Fenghua Yu @ 2026-08-31 17:22 UTC (permalink / raw)
To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
Will Deacon, Catalin Marinas, Shaopeng Tan, Chen Yu, Babu Moger,
Drew Fustini, Vikram Sethi, Shanker Donthineni, Newton Liu,
Richard Cheng
Cc: linux-kernel, linux-arm-kernel, Fenghua Yu
Memory MPAM MSCs on CPU-less NUMA nodes rely on mpam_ris_get_affinity()
falling back to cpu_possible_mask so the RIS can participate in MPAM
and resctrl control. Add a KUnit test that locates a CPU-less node when
present, exercises the MPAM_CLASS_MEMORY path, checks the reported
affinity equals cpu_possible_mask intersected with MSC accessibility,
and verifies cpu_less is set when the borrowed affinity is used.
Signed-off-by: Fenghua Yu <fenghuay@nvidia.com>
---
drivers/resctrl/test_mpam_devices.c | 67 +++++++++++++++++++++++++++++
1 file changed, 67 insertions(+)
diff --git a/drivers/resctrl/test_mpam_devices.c b/drivers/resctrl/test_mpam_devices.c
index 31871f519729..7054919d4574 100644
--- a/drivers/resctrl/test_mpam_devices.c
+++ b/drivers/resctrl/test_mpam_devices.c
@@ -382,9 +382,76 @@ static void test_mpam_reset_msc_bitmap(struct kunit *test)
mutex_unlock(&fake_msc.part_sel_lock);
}
+static u32 find_cpuless_numa_node(void)
+{
+ int node;
+ cpumask_var_t tmp;
+
+ if (!alloc_cpumask_var(&tmp, GFP_KERNEL))
+ return UINT_MAX;
+
+ for (node = 0; node < nr_node_ids; node++) {
+ cpumask_clear(tmp);
+ get_cpumask_from_node_id(node, tmp);
+ if (cpumask_empty(tmp)) {
+ u32 ret = node;
+
+ free_cpumask_var(tmp);
+ return ret;
+ }
+ }
+
+ free_cpumask_var(tmp);
+ return UINT_MAX;
+}
+
+static void test_mpam_ris_cpuless_numa_affinity(struct kunit *test)
+{
+ cpumask_var_t affinity, expected;
+ bool cpu_less = false;
+ u32 node;
+ int err;
+
+ node = find_cpuless_numa_node();
+ if (node == UINT_MAX) {
+ kunit_skip(test, "No CPU-less NUMA node on this platform");
+ return;
+ }
+
+ if (!alloc_cpumask_var(&affinity, GFP_KERNEL) ||
+ !alloc_cpumask_var(&expected, GFP_KERNEL)) {
+ free_cpumask_var(affinity);
+ free_cpumask_var(expected);
+ kunit_skip(test, "Failed to allocate cpumask");
+ return;
+ }
+
+ memset(&fake_msc1, 0, sizeof(fake_msc1));
+ fake_msc1.pdev = &fake_pdev;
+ cpumask_copy(&fake_msc1.accessibility, cpu_possible_mask);
+
+ fake_class.level = 3;
+ fake_class.type = MPAM_CLASS_MEMORY;
+ fake_comp1.comp_id = node;
+
+ cpumask_clear(affinity);
+ err = mpam_ris_get_affinity(&fake_msc1, affinity, MPAM_CLASS_MEMORY,
+ &fake_class, &fake_comp1, &cpu_less);
+ KUNIT_EXPECT_EQ(test, err, 0);
+ KUNIT_EXPECT_TRUE(test, cpu_less);
+
+ cpumask_copy(expected, cpu_possible_mask);
+ cpumask_and(expected, expected, &fake_msc1.accessibility);
+ KUNIT_EXPECT_TRUE(test, cpumask_equal(affinity, expected));
+
+ free_cpumask_var(affinity);
+ free_cpumask_var(expected);
+}
+
static struct kunit_case mpam_devices_test_cases[] = {
KUNIT_CASE(test_mpam_reset_msc_bitmap),
KUNIT_CASE(test_mpam_enable_merge_features),
+ KUNIT_CASE(test_mpam_ris_cpuless_numa_affinity),
KUNIT_CASE(test__props_mismatch),
{}
};
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [PATCH RFC v2 19/19] selftests/resctrl: Add MB emulation test for ARM MPAM
2026-08-31 17:22 [PATCH RFC v2 00/19] arm,fs/resctrl: ARM MPAM MB_NODE support Fenghua Yu
` (17 preceding siblings ...)
2026-08-31 17:22 ` [PATCH RFC v2 18/19] arm_mpam: Add KUnit test for CPU-less NUMA node affinity Fenghua Yu
@ 2026-08-31 17:22 ` Fenghua Yu
2026-09-01 9:37 ` [PATCH RFC v2 00/19] arm,fs/resctrl: ARM MPAM MB_NODE support Richard Cheng
19 siblings, 0 replies; 21+ messages in thread
From: Fenghua Yu @ 2026-08-31 17:22 UTC (permalink / raw)
To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
Will Deacon, Catalin Marinas, Shaopeng Tan, Chen Yu, Babu Moger,
Drew Fustini, Vikram Sethi, Shanker Donthineni, Newton Liu,
Richard Cheng
Cc: linux-kernel, linux-arm-kernel, Fenghua Yu
Add a kselftest that exercises MB control emulation on ARM MPAM:
info/MB/control_mode, the nested MB_NODE control under
info/MB/schemata/MB/, and schemata visibility when switching legacy
(MB:) and native (MB_NODE:) modes. The info/ schemata tree stays nested
across the switch.
Signed-off-by: Fenghua Yu <fenghuay@nvidia.com>
---
.../selftests/resctrl/mb_emulation_test.c | 314 ++++++++++++++++++
tools/testing/selftests/resctrl/resctrl.h | 1 +
.../testing/selftests/resctrl/resctrl_tests.c | 1 +
3 files changed, 316 insertions(+)
create mode 100644 tools/testing/selftests/resctrl/mb_emulation_test.c
diff --git a/tools/testing/selftests/resctrl/mb_emulation_test.c b/tools/testing/selftests/resctrl/mb_emulation_test.c
new file mode 100644
index 000000000000..845aa46fd092
--- /dev/null
+++ b/tools/testing/selftests/resctrl/mb_emulation_test.c
@@ -0,0 +1,314 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * MB (memory bandwidth) control emulation test (ARM MPAM)
+ *
+ * Exercises info/MB/control_mode, the nested MB_NODE control under
+ * info/MB/schemata/MB/, and schemata visibility when switching between
+ * legacy (MB:) and native (MB_NODE:) modes.
+ */
+#include <fcntl.h>
+#include <limits.h>
+
+#include "resctrl.h"
+
+#define MB_INFO INFO_PATH "/MB"
+#define MB_MODE_PATH MB_INFO "/control_mode"
+#define MB_SCHEMATA_DIR MB_INFO "/schemata"
+#define MB_CTRL_PATH MB_SCHEMATA_DIR "/MB"
+#define MB_NODE_NESTED_PATH MB_CTRL_PATH "/MB_NODE"
+#define MB_NODE_SIBLING_PATH MB_SCHEMATA_DIR "/MB_NODE"
+#define ROOT_SCHEMATA_PATH RESCTRL_PATH "/schemata"
+
+static int read_file(const char *path, char *buf, size_t buflen)
+{
+ ssize_t ret;
+ int fd, n;
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ return -errno;
+
+ ret = read(fd, buf, buflen - 1);
+ close(fd);
+ if (ret < 0)
+ return -errno;
+
+ n = (int)ret;
+ while (n > 0 && (buf[n - 1] == '\n' || buf[n - 1] == ' '))
+ n--;
+ buf[n] = '\0';
+
+ return 0;
+}
+
+static bool path_is_dir(const char *path)
+{
+ struct stat st;
+
+ if (stat(path, &st))
+ return false;
+
+ return S_ISDIR(st.st_mode);
+}
+
+static int mb_read_mode(char *buf, size_t len)
+{
+ return read_file(MB_MODE_PATH, buf, len);
+}
+
+static int mb_write_mode(const char *mode)
+{
+ char msg[32];
+ int fd, ret, len;
+
+ len = snprintf(msg, sizeof(msg), "%s\n", mode);
+ if (len < 0 || len >= (int)sizeof(msg))
+ return -EINVAL;
+
+ fd = open(MB_MODE_PATH, O_WRONLY);
+ if (fd < 0)
+ return -errno;
+
+ ret = write(fd, msg, len) == len ? 0 : -errno;
+ close(fd);
+
+ return ret;
+}
+
+static bool mb_mode_active_is(const char *mode_text, const char *mode)
+{
+ char pattern[32];
+
+ snprintf(pattern, sizeof(pattern), "[%s]", mode);
+ return strstr(mode_text, pattern);
+}
+
+static int mb_read_root_schemata(char *buf, size_t len)
+{
+ return read_file(ROOT_SCHEMATA_PATH, buf, len);
+}
+
+/*
+ * resctrl right-aligns schemata names with leading whitespace, so a control
+ * line looks like " MB:0=100...". Treat prefix as matching when it
+ * appears at the start of a line, ignoring any leading spaces or tabs.
+ */
+static const char *schemata_find_line(const char *schemata, const char *prefix)
+{
+ const char *p;
+
+ if (!schemata || !prefix)
+ return NULL;
+
+ for (p = strstr(schemata, prefix); p; p = strstr(p + 1, prefix)) {
+ const char *q = p;
+
+ while (q > schemata && (q[-1] == ' ' || q[-1] == '\t'))
+ q--;
+
+ if (q == schemata || q[-1] == '\n')
+ return p;
+ }
+
+ return NULL;
+}
+
+static bool schemata_has_prefix(const char *schemata, const char *prefix)
+{
+ return schemata_find_line(schemata, prefix);
+}
+
+static int mb_expect_nested_hierarchy(const char *why)
+{
+ if (!path_is_dir(MB_NODE_NESTED_PATH) || path_is_dir(MB_NODE_SIBLING_PATH)) {
+ ksft_print_msg("%s: MB_NODE should be nested, nested=%d sibling=%d\n",
+ why, path_is_dir(MB_NODE_NESTED_PATH),
+ path_is_dir(MB_NODE_SIBLING_PATH));
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int mb_test_mode_read(void)
+{
+ char mode[64];
+ int ret;
+
+ ret = mb_read_mode(mode, sizeof(mode));
+ if (ret)
+ return ret;
+
+ if (!mb_mode_active_is(mode, "legacy") && !mb_mode_active_is(mode, "native")) {
+ ksft_print_msg("mode file has no active mode: '%s'\n", mode);
+ return -EINVAL;
+ }
+
+ ksft_print_msg("mode file: %s\n", mode);
+ return 0;
+}
+
+static int mb_test_schemata_visibility(bool expect_mb_line, const char *mode)
+{
+ char schemata[4096];
+ bool has_mb, has_mb_node;
+ int ret;
+
+ ret = mb_read_root_schemata(schemata, sizeof(schemata));
+ if (ret)
+ return ret;
+
+ has_mb = schemata_has_prefix(schemata, "MB:");
+ has_mb_node = schemata_has_prefix(schemata, "MB_NODE:");
+
+ if (expect_mb_line) {
+ if (!has_mb) {
+ ksft_print_msg("%s: expected MB: line in schemata\n", mode);
+ return -EINVAL;
+ }
+ if (has_mb_node) {
+ ksft_print_msg("%s: MB_NODE: line should be hidden in schemata\n",
+ mode);
+ return -EINVAL;
+ }
+ } else {
+ if (has_mb) {
+ ksft_print_msg("%s: MB: line should be hidden in schemata\n",
+ mode);
+ return -EINVAL;
+ }
+ if (!has_mb_node) {
+ ksft_print_msg("%s: expected MB_NODE: line in schemata\n",
+ mode);
+ return -EINVAL;
+ }
+ }
+
+ ksft_print_msg("switched to %s: schemata MB:%d MB_NODE:%d\n",
+ mode, has_mb, has_mb_node);
+ return 0;
+}
+
+static int mb_restore_mode(const char *orig_mode)
+{
+ if (mb_mode_active_is(orig_mode, "legacy"))
+ return mb_write_mode("legacy");
+ if (mb_mode_active_is(orig_mode, "native"))
+ return mb_write_mode("native");
+
+ return 0;
+}
+
+static int mb_verify_active_mode(const char *mode)
+{
+ char buf[64];
+ int ret;
+
+ ret = mb_read_mode(buf, sizeof(buf));
+ if (ret)
+ return ret;
+
+ if (!mb_mode_active_is(buf, mode)) {
+ ksft_print_msg("mode switch to %s failed, mode file: '%s'\n",
+ mode, buf);
+ return -EINVAL;
+ }
+
+ ksft_print_msg("switched to %s: mode file='%s'\n", mode, buf);
+ return 0;
+}
+
+/*
+ * Switch to @mode and verify the mode file, the nested info/MB/schemata
+ * layout (unchanged by the switch), and root schemata visibility.
+ */
+static int mb_switch_and_check(const char *mode)
+{
+ bool legacy = !strcmp(mode, "legacy");
+ int ret;
+
+ ksft_print_msg("--- switching to %s mode ---\n", mode);
+
+ ret = mb_write_mode(mode);
+ if (ret) {
+ ksft_print_msg("failed to write %s mode\n", mode);
+ return ret;
+ }
+
+ ret = mb_verify_active_mode(mode);
+ if (ret)
+ return ret;
+
+ ret = mb_expect_nested_hierarchy(mode);
+ if (ret)
+ return ret;
+
+ return mb_test_schemata_visibility(legacy, mode);
+}
+
+static int mb_emulation_run_test(const struct resctrl_test *test,
+ const struct user_params *uparams)
+{
+ char orig_mode[64];
+ int ret;
+
+ (void)test;
+ (void)uparams;
+
+ ret = mb_expect_nested_hierarchy("initial");
+ if (ret)
+ return ret;
+
+ ret = mb_read_mode(orig_mode, sizeof(orig_mode));
+ if (ret) {
+ ksft_print_msg("failed to read %s\n", MB_MODE_PATH);
+ return ret;
+ }
+
+ ret = mb_test_mode_read();
+ if (ret)
+ goto out_restore;
+
+ /* Cycle legacy -> native -> legacy, checking each transition. */
+ ret = mb_switch_and_check("legacy");
+ if (ret)
+ goto out_restore;
+
+ ret = mb_switch_and_check("native");
+ if (ret)
+ goto out_restore;
+
+ ret = mb_switch_and_check("legacy");
+ if (ret)
+ goto out_restore;
+
+out_restore:
+ if (mb_restore_mode(orig_mode))
+ ksft_print_msg("warning: failed to restore original mode\n");
+
+ return ret;
+}
+
+static bool mb_emulation_feature_check(const struct resctrl_test *test)
+{
+ (void)test;
+
+ if (!resctrl_resource_exists("MB"))
+ return false;
+
+ if (!resource_info_file_exists("MB", "control_mode"))
+ return false;
+
+ if (!path_is_dir(MB_SCHEMATA_DIR) || !path_is_dir(MB_CTRL_PATH))
+ return false;
+
+ return path_is_dir(MB_NODE_NESTED_PATH);
+}
+
+struct resctrl_test mb_emulation_test = {
+ .name = "mb_emulation",
+ .group = "mb",
+ .resource = "MB",
+ .feature_check = mb_emulation_feature_check,
+ .run_test = mb_emulation_run_test,
+};
diff --git a/tools/testing/selftests/resctrl/resctrl.h b/tools/testing/selftests/resctrl/resctrl.h
index 175101022bf3..ad1c17c0b0bf 100644
--- a/tools/testing/selftests/resctrl/resctrl.h
+++ b/tools/testing/selftests/resctrl/resctrl.h
@@ -242,5 +242,6 @@ extern struct resctrl_test cmt_test;
extern struct resctrl_test l3_cat_test;
extern struct resctrl_test l3_noncont_cat_test;
extern struct resctrl_test l2_noncont_cat_test;
+extern struct resctrl_test mb_emulation_test;
#endif /* RESCTRL_H */
diff --git a/tools/testing/selftests/resctrl/resctrl_tests.c b/tools/testing/selftests/resctrl/resctrl_tests.c
index dbcd5eea9fbc..593f0ca5251b 100644
--- a/tools/testing/selftests/resctrl/resctrl_tests.c
+++ b/tools/testing/selftests/resctrl/resctrl_tests.c
@@ -17,6 +17,7 @@ volatile int *value_sink = &sink_target;
static struct resctrl_test *resctrl_tests[] = {
&mbm_test,
&mba_test,
+ &mb_emulation_test,
&cmt_test,
&l3_cat_test,
&l3_noncont_cat_test,
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* Re: [PATCH RFC v2 00/19] arm,fs/resctrl: ARM MPAM MB_NODE support
2026-08-31 17:22 [PATCH RFC v2 00/19] arm,fs/resctrl: ARM MPAM MB_NODE support Fenghua Yu
` (18 preceding siblings ...)
2026-08-31 17:22 ` [PATCH RFC v2 19/19] selftests/resctrl: Add MB emulation test for ARM MPAM Fenghua Yu
@ 2026-09-01 9:37 ` Richard Cheng
19 siblings, 0 replies; 21+ messages in thread
From: Richard Cheng @ 2026-09-01 9:37 UTC (permalink / raw)
To: Fenghua Yu
Cc: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
Will Deacon, Catalin Marinas, Shaopeng Tan, Chen Yu, Babu Moger,
Drew Fustini, Vikram Sethi, Shanker Donthineni, Newton Liu,
linux-kernel, linux-arm-kernel
On Mon, Aug 31, 2026 at 10:22:26AM +0800, Fenghua Yu wrote:
> This series enables MBA bandwidth control emulation on ARM MPAM when the
> default MB control is disabled, using a node-scoped MB_NODE control as
> the native backend. It extends memory-bandwidth monitoring and
> control to memory-level MSCs or CPU-less NUMA nodes, and adds
> documentation and tests.
>
> Background
>
> On x86, MBA bandwidth control is typically associated with the L3 cache:
> schemata lines use L3 cache ids and monitor domains appear as mon_L3_XX.
> On some ARM MPAM systems the MBA control is backed by a memory-level MSC
> rather than the L3 cache MSC. A memory-level MSC is represented as a
> NUMA node, and that node commonly has no CPUs of its own (a memory-only
> node that still participates in bandwidth control). For such a resource
> the control scope is NODE, schemata identifiers are NUMA node ids,
> monitor domains are named mon_NODE_XX, and a node-scoped MB_NODE control
> may be the only control with usable bandwidth hardware.
>
> Because the memory-level MSC lives on a CPU-less NUMA node, the driver
> must handle nodes with no local CPUs. mpam_ris_get_affinity() derives
> affinity from the component's NUMA node id, so a memory-only node would
> otherwise end up with an empty CPU mask and never be registered. When
> the derived mask is empty, the driver falls back to cpu_possible_mask
> (masked by MSC accessibility) so the node still gets a resctrl domain.
> The borrowed affinity is kept out of class->affinity: those CPUs already
> contribute through CPU-ful nodes in the class, and subtracting the
> borrowed mask on teardown would remove CPUs that other live nodes still
> depend on. Domain setup iterates all MSC components per CPU (keyed by
> component), so each component — including these CPU-less memory nodes —
> gets its own control and monitor domain.
>
> Examples of NODE and emulation interfaces on MPAM.
>
> ** On platform with CPU-less nodes, by default, control_mode is legacy.
> 1. By default, MB is emulated by MB_NODE in schemata.
> info/
> ├── MB
> │ ├── bandwidth_gran
> │ ├── control_mode # [legacy] native
> │ └── schemata
> │ └── MB
> │ ├── MB_NODE # emulated by "MB_NODE"
>
> schemata: Only show MB, which is emulated by MB_NODE. domain id is numa
> id
> MB:0=100;1=100;2=100;10=100;18=100;26=100;34=100;35=100
> L3:1=ffff;2=ffff
>
> 2. root can switch to native by writing "native" to "control_mode":
> info/
> ├── MB
> │ ├── bandwidth_gran
> │ ├── control_mode # legacy [native]
> │ └── schemata
> │ └── MB
> │ ├── MB_NODE # no emulation, native mode
>
> schemata: Only show MB_NODE. No "MB:" is emulated.
> MB_NODE:0=100;1=100;2=100;10=100;18=100;26=100;34=100;35=100
> L3:1=ffff;2=ffff
>
> 3. llc_occupancy in mon_L3_x where x is cache id
> mbm_total_bytes in mon_NODE_y where y is numa id
> mon_data/
> mon_L3_01 mon_NODE_00 mon_NODE_02 mon_NODE_18 mon_NODE_34
> mon_L3_02 mon_NODE_01 mon_NODE_10 mon_NODE_26 mon_NODE_35
>
> mon_data/mon_L3_01/llc_occupancy
> mon_data/mon_NODE_01/mbm_total_bytes
>
> ** On legacy machine which only has L3 MSCs. No NODE scope in schemata
> or mon_data
> schemata: MB's domain id is cache id, not numa node id
> MB:1=100;2=100
> L3:1=ffff;2=ffff
>
> mon_data/
> mon_L3_01 mon_L3_02
>
> mon_data/mon_L3_01/llc_occupancy
> mon_data/mon_L3_01/mbm_total_bytes
> mon_data/mon_L3_02/llc_occupancy
> mon_data/mon_L3_02/mbm_total_bytes
>
> The patches can be compiled but not tested on x86 yet.
>
> The 19 patches are grouped as follows:
>
> 1. Memory bandwidth monitoring and control on memory-level MSCs
> (patches 1–10, 12)
>
> Generalise the monitor infrastructure beyond hard-wired L3 paths:
> de-hardcode L3 monitor plumbing, expose MBA MBM counter assignment,
> name node-scoped monitor domains mon_NODE_<id>, add a node-scope MBM
> total event, and make MBM handling resource-aware.
>
> On the ARM MPAM side, add memory-level MSC and ABMC support, refine
> L3 topology and class selection, refactor domain setup to iterate all
> MSC components per CPU (keyed by component), and handle CPU-less NUMA
> memory nodes that have no local CPUs but still participate in MPAM
> bandwidth control. Memory hotplug locking and notifier are added.
>
> 4. MB_NODE emulates MB (patch 11) [1]
>
> Wire MB_NODE as the native backend that emulates a MB control
> in the ARM MPAM driver.
>
> If emulation is unnecessary, this patch can be removed.
>
> 5. Documentation (patches 14–17)
>
> Document memory-level MB control and NUMA nodes in the arm64 MPAM
> guide (patch 19), NODE-scoped MBA domains and mon_NODE_* monitoring
> in the resctrl documentation (patch 20), and an MB_NODE emulation
> example on ARM MPAM (patch 21).
>
> 6. Tests (patches 18–19)
>
> Add a KUnit test for CPU-less NUMA node affinity handling and a
> kselftest that exercises MB emulation mode switching,
> resource_schemata hierarchy, and schemata mirroring on ARM MPAM.
>
> Apply the patches on top of [2].
>
> Testing
>
> # KUnit (CPU-less affinity)
> sudo cat /sys/kernel/debug/kunit/mpam_devices_test_suite/results
>
>
> # kselftest (MB emulation; requires ARM MPAM with disabled MB control)
> cd tools/testing/selftests/resctrl
> sudo ./resctrl_tests -t mb_emulation
>
> This series is in https://github.com/fyu1/linux/ cpu_less.rfc.v2/
> which is on top of [2].
>
Hi Fenghua,
I think the high-level direction looks good.
Just have some question about lifecycle model and object model.
Now MBM event and ABMC state are still partly global. For example, mpam_resctrl_mbm_total_mon()
selects one preferred total-bandwidth event, while the assignment interface redirects or hides
mbm_L3_assignment when node monitoring is present. On a platform exposing both events, L3 init
can consume NODE state, and userspace has no independent endpoint for the real L3 assignment.
the idea of borrowing of cpumask is interesting, but it's now used for several different concept:
domain membership, HW accessibility and worker placement. Borrowing a CPU mask for a CPU-less node
thus will make multiple node domains overlap, while generic lookup and worker-migration code still
assumes that a CPU identifies one monitor domain.
And should we make domain lifetime have an owner ? it's now shared between CPU hotplug and memory-node
hotplug for the complete transition.
If we can make these concept fixed or even clearer, I think it would be amazing, thanks.
Best regards,
Richard Cheng.
> Change Log:
> RFC v2:
> - Remove fix patches 1-2 which are implemented in [2]
> - Remove the emulation patches 3-8 which are implemented in [2]
> - Update emulated_by to *emulated_by in patch 11. Ben seems
> doesn't like to emulate MB by MB_NODE. If that's case, I can
> remove this patch. But for now, I keep this patch for further
> discussion.
> - Add patch 13 to implemente legacy and native modes to emulate
> "mbm_L3_assignments" by "mbm_NODE_assignments" in patch 13.
> - Add patch 9 for memory hotplug locking (Ben)
> - Add patch 12 for memory hotplug notifier (Ben)
>
> RFC v1:
> https://lore.kernel.org/lkml/cover.1784217438.git.fenghuay@nvidia.com/
>
> [1] [RFC v2] mpam,x86,fs/resctrl: Generic schema description Proof of Concept https://lore.kernel.org/lkml/d258a32f-12d5-464d-abe9-4720fb3e44b3@intel.com/
> [2] Reinette's resctrl control branch RFC v2: git://git.kernel.org/pub/scm/linux/kernel/git/reinette/linux.git branch resctrl/controls_rfc_v2.4
>
> Fenghua Yu (17):
> resctrl: De-hardcode L3 monitor infrastructure
> resctrl: Expose MBA MBM counter assignment sysfs
> resctrl: name node-scoped monitor domains mon_NODE_<id>
> resctrl: Add node-scope MBM total event
> resctrl: Make MBM paths resource-aware
> arm_mpam: Support memory-level MSCs and ABMC per class
> arm_mpam: Refine L3 topology and class selection
> arm_mpam: Include all MSC components during domain setup
> arm_mpam: Handle CPU-less numa nodes
> arm_mpam: Emulate MB control with node-scoped MB_NODE control
> resctrl: Add mbm_assign_scope_mode for native assignment file names
> Documentation: resctrl: document mbm_assign_scope_mode
> Documentation: arm64: mpam: document memory-level MB control and NUMA
> nodes
> Documentation: resctrl: document NODE-scoped MBA domains and mon_NODE
> monitoring
> Documentation: resctrl: document MB_NODE emulation example on ARM MPAM
> arm_mpam: Add KUnit test for CPU-less NUMA node affinity
> selftests/resctrl: Add MB emulation test for ARM MPAM
>
> James Morse (2):
> fs/resctrl: Take memory hotplug lock whenever taking CPU hotplug lock
> arm_mpam: resctrl: Add NUMA node notifier for domain online/offline
>
> Documentation/arch/arm64/mpam.rst | 105 ++-
> Documentation/filesystems/resctrl.rst | 135 ++-
> drivers/resctrl/mpam_devices.c | 59 +-
> drivers/resctrl/mpam_internal.h | 8 +
> drivers/resctrl/mpam_resctrl.c | 879 +++++++++++++++---
> drivers/resctrl/test_mpam_devices.c | 67 ++
> fs/resctrl/ctrlmondata.c | 6 +-
> fs/resctrl/internal.h | 18 +-
> fs/resctrl/monitor.c | 355 +++++--
> fs/resctrl/pseudo_lock.c | 3 +
> fs/resctrl/rdtgroup.c | 157 +++-
> include/linux/memory.h | 1 +
> include/linux/resctrl.h | 36 +-
> include/linux/resctrl_types.h | 15 +-
> .../selftests/resctrl/mb_emulation_test.c | 314 +++++++
> tools/testing/selftests/resctrl/resctrl.h | 1 +
> .../testing/selftests/resctrl/resctrl_tests.c | 1 +
> 17 files changed, 1827 insertions(+), 333 deletions(-)
> create mode 100644 tools/testing/selftests/resctrl/mb_emulation_test.c
>
> --
> 2.53.0
>
^ permalink raw reply [flat|nested] 21+ messages in thread