mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH RFC v3 00/15] arm,fs/resctrl: ARM MPAM memory-level MSC and CPU-less NUMA nodes
@ 2026-11-09 21:24 Fenghua Yu
  2026-11-09 21:24 ` [PATCH RFC v3 01/15] resctrl: De-hardcode L3 monitor infrastructure Fenghua Yu
                   ` (14 more replies)
  0 siblings, 15 replies; 16+ messages in thread
From: Fenghua Yu @ 2026-11-09 21:24 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Will Deacon,
	Dave Martin, 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

This series extends memory-bandwidth monitoring and control to ARM MPAM
memory-level MSCs, including CPU-less NUMA nodes, and documents the
resulting resctrl interfaces.

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 and MBWU counters are 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 that resource the schemata line is MB_NODE and its identifiers are
NUMA node ids.  Monitor domains are named mon_NODE_XX.  MBM counter
assignment is exposed as mbm_NODE_assignments.  An L3-cache MSC keeps the
MB schemata line, mon_L3_XX domains, and mbm_L3_assignments.

info/MB/control_mode stays legacy.  emulated_by is left empty, so legacy
and native mode expose the same schemata line.  MB is not emulated by
MB_NODE, and the assignment file name does not follow control_mode.

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.  A memory hotplug lock and a
NUMA node notifier bring those domains online and offline.

Examples

** Memory-level MSC (for example a platform with CPU-less nodes).
   control_mode is legacy by default.  Both legacy and native mode show
   the same schemata line.  Domain ids are NUMA node ids.

info/MB/control_mode          # [legacy] native
info/MB/schemata/MB_NODE

schemata:
MB_NODE:0=100;1=100;2=100;10=100;18=100;26=100;34=100;35=100
     L3:1=ffff;2=ffff

mon_data/mon_L3_01/llc_occupancy
mon_data/mon_NODE_01/mbm_total_bytes

CTRL_MON and MON groups expose mbm_NODE_assignments when MBM is backed
by the NUMA-node MSC.

** L3-cache MSC only (no memory-level MSC).  Both modes show MB:.
   Domain ids are L3 cache ids.

info/MB/schemata/MB

schemata:
MB:1=100;2=100
L3:1=ffff;2=ffff

mon_data/mon_L3_01/llc_occupancy
mon_data/mon_L3_01/mbm_total_bytes

CTRL_MON and MON groups expose mbm_L3_assignments.

The patches can be compiled but not tested on x86 yet.

The 15 patches are grouped as follows:

1. resctrl monitor infrastructure (patches 1-5)

   Generalise the monitor infrastructure beyond hard-wired L3 paths:
   de-hardcode L3 monitor plumbing, expose MBA MBM counter assignment as
   mbm_NODE_assignments, name node-scoped monitor domains mon_NODE_<id>,
   add a node-scope MBM total event, and make MBM handling resource-aware.

2. ARM MPAM memory-level MSC (patches 6-8)

   Map monitor classes to L3 or MBA, initialise ABMC on the resource that
   owns the MBWU monitor, refine class selection so a memory class can
   back MBWU, and iterate all MSC components per CPU when creating
   domains.  A memory-class MSC names its control NODE, so the schemata
   line is MB_NODE.  A cache MSC keeps MB.

3. CPU-less NUMA nodes (patches 9-11)

   Take the memory hotplug lock with the CPU hotplug lock, fall back to
   cpu_possible_mask for a memory MSC with no local CPUs, and add a NUMA
   node notifier so CPU-less domains come online and offline.

4. Documentation (patches 12-14)

   Document mbm_NODE_assignments, memory-level MB control and NUMA nodes
   in the arm64 MPAM guide, and MB_NODE schemata lines and mon_NODE_*
   monitoring in the resctrl documentation.

5. Test (patch 15)

   Add a KUnit test for CPU-less NUMA node affinity handling.

Apply the patches on top of [1].

Testing

  # KUnit (CPU-less affinity)
  sudo cat /sys/kernel/debug/kunit/mpam_devices_test_suite/results

This series is in https://github.com/fyu1/linux/  cpu_less.rfc.v3/
which is on top of [1].

Change Log:
RFC v3:
- Drop MB control emulation.  MB is no longer emulated by MB_NODE.
  control_mode stays legacy and emulated_by is empty, so legacy and
  native mode show the same schemata line.  A memory-level MSC shows
  "MB_NODE:" and uses NUMA node ids.  An L3-cache MSC shows "MB:" and
  uses L3 cache ids (Ben).
- Drop the MB emulation kselftest and the emulation documentation (Ben).
- Drop mbm_assign_scope_mode.  "mbm_NODE_assignments" is exposed when
  MBM is backed by a NUMA-node MSC.  "mbm_L3_assignments" is exposed
  when MBM is backed by an L3-cache MSC.  The file name does not follow
  control_mode.
- Series is now 15 patches.

RFC v2:
- Remove fix patches 1-2 which are implemented in [1]
- Remove the emulation patches 3-8 which are implemented in [1]
- 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 implement 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 v2:
https://lore.kernel.org/lkml/20260831172245.42253-1-fenghuay@nvidia.com/

RFC v1:
https://lore.kernel.org/lkml/cover.1784217438.git.fenghuay@nvidia.com/

[1] 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 (13):
  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
  Documentation: resctrl: document mbm_NODE_assignments
  Documentation: arm64: mpam: document memory-level MB control and NUMA
    nodes
  Documentation: resctrl: document NUMA-node MBA domains and mon_NODE
    monitoring
  arm_mpam: Add KUnit test for CPU-less NUMA node affinity

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     |  95 ++--
 Documentation/filesystems/resctrl.rst |  54 ++-
 drivers/resctrl/mpam_devices.c        |  59 ++-
 drivers/resctrl/mpam_internal.h       |   8 +
 drivers/resctrl/mpam_resctrl.c        | 664 +++++++++++++++++++++-----
 drivers/resctrl/test_mpam_devices.c   |  67 +++
 fs/resctrl/ctrlmondata.c              |   6 +-
 fs/resctrl/internal.h                 |   9 +-
 fs/resctrl/monitor.c                  | 226 ++++++---
 fs/resctrl/pseudo_lock.c              |   3 +
 fs/resctrl/rdtgroup.c                 | 148 ++++--
 include/linux/memory.h                |   1 +
 include/linux/resctrl.h               |  31 +-
 include/linux/resctrl_types.h         |  15 +-
 14 files changed, 1079 insertions(+), 307 deletions(-)

-- 
2.53.0

^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH RFC v3 01/15] resctrl: De-hardcode L3 monitor infrastructure
  2026-11-09 21:24 [PATCH RFC v3 00/15] arm,fs/resctrl: ARM MPAM memory-level MSC and CPU-less NUMA nodes Fenghua Yu
@ 2026-11-09 21:24 ` Fenghua Yu
  2026-11-09 21:24 ` [PATCH RFC v3 02/15] resctrl: Expose MBA MBM counter assignment sysfs Fenghua Yu
                   ` (13 subsequent siblings)
  14 siblings, 0 replies; 16+ messages in thread
From: Fenghua Yu @ 2026-11-09 21:24 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Will Deacon,
	Dave Martin, 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	[flat|nested] 16+ messages in thread

* [PATCH RFC v3 02/15] resctrl: Expose MBA MBM counter assignment sysfs
  2026-11-09 21:24 [PATCH RFC v3 00/15] arm,fs/resctrl: ARM MPAM memory-level MSC and CPU-less NUMA nodes Fenghua Yu
  2026-11-09 21:24 ` [PATCH RFC v3 01/15] resctrl: De-hardcode L3 monitor infrastructure Fenghua Yu
@ 2026-11-09 21:24 ` Fenghua Yu
  2026-11-09 21:24 ` [PATCH RFC v3 03/15] resctrl: name node-scoped monitor domains mon_NODE_<id> Fenghua Yu
                   ` (12 subsequent siblings)
  14 siblings, 0 replies; 16+ messages in thread
From: Fenghua Yu @ 2026-11-09 21:24 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Will Deacon,
	Dave Martin, 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	[flat|nested] 16+ messages in thread

* [PATCH RFC v3 03/15] resctrl: name node-scoped monitor domains mon_NODE_<id>
  2026-11-09 21:24 [PATCH RFC v3 00/15] arm,fs/resctrl: ARM MPAM memory-level MSC and CPU-less NUMA nodes Fenghua Yu
  2026-11-09 21:24 ` [PATCH RFC v3 01/15] resctrl: De-hardcode L3 monitor infrastructure Fenghua Yu
  2026-11-09 21:24 ` [PATCH RFC v3 02/15] resctrl: Expose MBA MBM counter assignment sysfs Fenghua Yu
@ 2026-11-09 21:24 ` Fenghua Yu
  2026-11-09 21:24 ` [PATCH RFC v3 04/15] resctrl: Add node-scope MBM total event Fenghua Yu
                   ` (11 subsequent siblings)
  14 siblings, 0 replies; 16+ messages in thread
From: Fenghua Yu @ 2026-11-09 21:24 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Will Deacon,
	Dave Martin, 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	[flat|nested] 16+ messages in thread

* [PATCH RFC v3 04/15] resctrl: Add node-scope MBM total event
  2026-11-09 21:24 [PATCH RFC v3 00/15] arm,fs/resctrl: ARM MPAM memory-level MSC and CPU-less NUMA nodes Fenghua Yu
                   ` (2 preceding siblings ...)
  2026-11-09 21:24 ` [PATCH RFC v3 03/15] resctrl: name node-scoped monitor domains mon_NODE_<id> Fenghua Yu
@ 2026-11-09 21:24 ` Fenghua Yu
  2026-11-09 21:24 ` [PATCH RFC v3 05/15] resctrl: Make MBM paths resource-aware Fenghua Yu
                   ` (10 subsequent siblings)
  14 siblings, 0 replies; 16+ messages in thread
From: Fenghua Yu @ 2026-11-09 21:24 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Will Deacon,
	Dave Martin, 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	[flat|nested] 16+ messages in thread

* [PATCH RFC v3 05/15] resctrl: Make MBM paths resource-aware
  2026-11-09 21:24 [PATCH RFC v3 00/15] arm,fs/resctrl: ARM MPAM memory-level MSC and CPU-less NUMA nodes Fenghua Yu
                   ` (3 preceding siblings ...)
  2026-11-09 21:24 ` [PATCH RFC v3 04/15] resctrl: Add node-scope MBM total event Fenghua Yu
@ 2026-11-09 21:24 ` Fenghua Yu
  2026-11-09 21:24 ` [PATCH RFC v3 06/15] arm_mpam: Support memory-level MSCs and ABMC per class Fenghua Yu
                   ` (9 subsequent siblings)
  14 siblings, 0 replies; 16+ messages in thread
From: Fenghua Yu @ 2026-11-09 21:24 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Will Deacon,
	Dave Martin, 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	[flat|nested] 16+ messages in thread

* [PATCH RFC v3 06/15] arm_mpam: Support memory-level MSCs and ABMC per class
  2026-11-09 21:24 [PATCH RFC v3 00/15] arm,fs/resctrl: ARM MPAM memory-level MSC and CPU-less NUMA nodes Fenghua Yu
                   ` (4 preceding siblings ...)
  2026-11-09 21:24 ` [PATCH RFC v3 05/15] resctrl: Make MBM paths resource-aware Fenghua Yu
@ 2026-11-09 21:24 ` Fenghua Yu
  2026-11-09 21:24 ` [PATCH RFC v3 07/15] arm_mpam: Refine L3 topology and class selection Fenghua Yu
                   ` (8 subsequent siblings)
  14 siblings, 0 replies; 16+ messages in thread
From: Fenghua Yu @ 2026-11-09 21:24 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Will Deacon,
	Dave Martin, 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	[flat|nested] 16+ messages in thread

* [PATCH RFC v3 07/15] arm_mpam: Refine L3 topology and class selection
  2026-11-09 21:24 [PATCH RFC v3 00/15] arm,fs/resctrl: ARM MPAM memory-level MSC and CPU-less NUMA nodes Fenghua Yu
                   ` (5 preceding siblings ...)
  2026-11-09 21:24 ` [PATCH RFC v3 06/15] arm_mpam: Support memory-level MSCs and ABMC per class Fenghua Yu
@ 2026-11-09 21:24 ` Fenghua Yu
  2026-11-09 21:24 ` [PATCH RFC v3 08/15] arm_mpam: Include all MSC components during domain setup Fenghua Yu
                   ` (7 subsequent siblings)
  14 siblings, 0 replies; 16+ messages in thread
From: Fenghua Yu @ 2026-11-09 21:24 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Will Deacon,
	Dave Martin, 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	[flat|nested] 16+ messages in thread

* [PATCH RFC v3 08/15] arm_mpam: Include all MSC components during domain setup
  2026-11-09 21:24 [PATCH RFC v3 00/15] arm,fs/resctrl: ARM MPAM memory-level MSC and CPU-less NUMA nodes Fenghua Yu
                   ` (6 preceding siblings ...)
  2026-11-09 21:24 ` [PATCH RFC v3 07/15] arm_mpam: Refine L3 topology and class selection Fenghua Yu
@ 2026-11-09 21:24 ` Fenghua Yu
  2026-11-09 21:24 ` [PATCH RFC v3 09/15] fs/resctrl: Take memory hotplug lock whenever taking CPU hotplug lock Fenghua Yu
                   ` (6 subsequent siblings)
  14 siblings, 0 replies; 16+ messages in thread
From: Fenghua Yu @ 2026-11-09 21:24 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Will Deacon,
	Dave Martin, 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.

A memory-class MSC keeps control_mode legacy and names its control NODE,
so the schemata line is MB_NODE in both legacy and native mode. A cache
MSC keeps the MB line.

Signed-off-by: Fenghua Yu <fenghuay@nvidia.com>
---
 drivers/resctrl/mpam_resctrl.c | 209 +++++++++++++++++++--------------
 fs/resctrl/ctrlmondata.c       |   1 +
 include/linux/resctrl.h        |   3 +
 3 files changed, 127 insertions(+), 86 deletions(-)

diff --git a/drivers/resctrl/mpam_resctrl.c b/drivers/resctrl/mpam_resctrl.c
index 72302f6021d7..ac0c488abe12 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;
+
+		if (r->mon_capable)
+			return true;
+	}
 
-	/* All monitors are presented as being on the L3 cache */
-	return l3->mon_capable;
+	return false;
 }
 
 bool resctrl_arch_is_evt_configurable(enum resctrl_event_id evt)
@@ -1293,8 +1309,18 @@ static int mpam_resctrl_control_init(struct mpam_resctrl_res *res)
 		break;
 	case RDT_RESOURCE_MBA:
 		r->ctrl_scope = RESCTRL_L3_CACHE;
+		/*
+		 * control_mode stays legacy. emulated_by is left empty so
+		 * legacy and native mode expose the same schemata line.
+		 * A memory-class MSC is node-scoped, so that line is
+		 * MB_NODE. A cache MSC keeps the MB line.
+		 */
+		r->ctrl_mode = RESCTRL_CTRL_MODE_LEGACY;
 		mpam_ctrl->r_ctrl.type = RESCTRL_CTRL_SCALAR;
-		mpam_ctrl->r_ctrl.name = RESCTRL_CTRL_NAME_DEF;
+		if (mpam_class_memory(class))
+			mpam_ctrl->r_ctrl.name = RESCTRL_CTRL_NAME_NODE;
+		else
+			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);
 
@@ -1731,13 +1757,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 +1770,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 +1798,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 +1814,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 +1836,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 +1873,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 +1906,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 +1929,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 +1940,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 +1987,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 +2003,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);
-			}
-		}
 	}
 }
 
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..3c4675656b51 100644
--- a/include/linux/resctrl.h
+++ b/include/linux/resctrl.h
@@ -398,6 +398,8 @@ 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". With resource name "MB" the schemata
+ *				line is "MB_NODE".
  * @RESCTRL_CTRL_NAME_REGION0_MIN:	"REGION0_MIN"
  * @RESCTRL_CTRL_NAME_REGION0_MAX:	"REGION0_MAX"
  */
@@ -405,6 +407,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	[flat|nested] 16+ messages in thread

* [PATCH RFC v3 09/15] fs/resctrl: Take memory hotplug lock whenever taking CPU hotplug lock
  2026-11-09 21:24 [PATCH RFC v3 00/15] arm,fs/resctrl: ARM MPAM memory-level MSC and CPU-less NUMA nodes Fenghua Yu
                   ` (7 preceding siblings ...)
  2026-11-09 21:24 ` [PATCH RFC v3 08/15] arm_mpam: Include all MSC components during domain setup Fenghua Yu
@ 2026-11-09 21:24 ` Fenghua Yu
  2026-11-09 21:24 ` [PATCH RFC v3 10/15] arm_mpam: Handle CPU-less numa nodes Fenghua Yu
                   ` (5 subsequent siblings)
  14 siblings, 0 replies; 16+ messages in thread
From: Fenghua Yu @ 2026-11-09 21:24 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Will Deacon,
	Dave Martin, 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	[flat|nested] 16+ messages in thread

* [PATCH RFC v3 10/15] arm_mpam: Handle CPU-less numa nodes
  2026-11-09 21:24 [PATCH RFC v3 00/15] arm,fs/resctrl: ARM MPAM memory-level MSC and CPU-less NUMA nodes Fenghua Yu
                   ` (8 preceding siblings ...)
  2026-11-09 21:24 ` [PATCH RFC v3 09/15] fs/resctrl: Take memory hotplug lock whenever taking CPU hotplug lock Fenghua Yu
@ 2026-11-09 21:24 ` Fenghua Yu
  2026-11-09 21:24 ` [PATCH RFC v3 11/15] arm_mpam: resctrl: Add NUMA node notifier for domain online/offline Fenghua Yu
                   ` (4 subsequent siblings)
  14 siblings, 0 replies; 16+ messages in thread
From: Fenghua Yu @ 2026-11-09 21:24 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Will Deacon,
	Dave Martin, 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	[flat|nested] 16+ messages in thread

* [PATCH RFC v3 11/15] arm_mpam: resctrl: Add NUMA node notifier for domain online/offline
  2026-11-09 21:24 [PATCH RFC v3 00/15] arm,fs/resctrl: ARM MPAM memory-level MSC and CPU-less NUMA nodes Fenghua Yu
                   ` (9 preceding siblings ...)
  2026-11-09 21:24 ` [PATCH RFC v3 10/15] arm_mpam: Handle CPU-less numa nodes Fenghua Yu
@ 2026-11-09 21:24 ` Fenghua Yu
  2026-11-09 21:24 ` [PATCH RFC v3 12/15] Documentation: resctrl: document mbm_NODE_assignments Fenghua Yu
                   ` (3 subsequent siblings)
  14 siblings, 0 replies; 16+ messages in thread
From: Fenghua Yu @ 2026-11-09 21:24 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Will Deacon,
	Dave Martin, 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 | 321 ++++++++++++++++++++++++++++++++-
 include/linux/memory.h         |   1 +
 2 files changed, 315 insertions(+), 7 deletions(-)

diff --git a/drivers/resctrl/mpam_resctrl.c b/drivers/resctrl/mpam_resctrl.c
index ac0c488abe12..8a169676c4ac 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>
@@ -1681,16 +1683,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,
@@ -1711,12 +1722,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();
@@ -1726,6 +1737,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)
 {
@@ -1741,6 +1758,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;
@@ -1925,6 +1959,275 @@ mpam_resctrl_get_ctrl_domain_from_cpu(int cpu, struct mpam_resctrl_res *res,
 	return NULL;
 }
 
+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;
+	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);
+			}
+		}
+	}
+
+	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;
+	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) {
+			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;
@@ -2088,6 +2391,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	[flat|nested] 16+ messages in thread

* [PATCH RFC v3 12/15] Documentation: resctrl: document mbm_NODE_assignments
  2026-11-09 21:24 [PATCH RFC v3 00/15] arm,fs/resctrl: ARM MPAM memory-level MSC and CPU-less NUMA nodes Fenghua Yu
                   ` (10 preceding siblings ...)
  2026-11-09 21:24 ` [PATCH RFC v3 11/15] arm_mpam: resctrl: Add NUMA node notifier for domain online/offline Fenghua Yu
@ 2026-11-09 21:24 ` Fenghua Yu
  2026-11-09 21:24 ` [PATCH RFC v3 13/15] Documentation: arm64: mpam: document memory-level MB control and NUMA nodes Fenghua Yu
                   ` (2 subsequent siblings)
  14 siblings, 0 replies; 16+ messages in thread
From: Fenghua Yu @ 2026-11-09 21:24 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Will Deacon,
	Dave Martin, 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

MBM counter assignment uses mbm_NODE_assignments when the counters are
backed by a NUMA-node MSC. An L3-cache MSC keeps mbm_L3_assignments.
Document both files.

Signed-off-by: Fenghua Yu <fenghuay@nvidia.com>
---
 Documentation/filesystems/resctrl.rst | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/Documentation/filesystems/resctrl.rst b/Documentation/filesystems/resctrl.rst
index 434e6f42db4a..6ead819a7a2a 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 in each CTRL_MON and MON
+	group: "mbm_L3_assignments" when MBM is backed by an L3-cache MSC, or
+	"mbm_NODE_assignments" when MBM is backed by a NUMA-node MSC.
+	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.
 
@@ -859,9 +861,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
+	exposed when MBM is backed by an L3-cache MSC.
+	"mbm_NODE_assignments" is exposed when MBM is backed by a NUMA-node
+	MSC. The file format is the same.
 
 	The assignment list is displayed in the following format:
 
-- 
2.53.0


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH RFC v3 13/15] Documentation: arm64: mpam: document memory-level MB control and NUMA nodes
  2026-11-09 21:24 [PATCH RFC v3 00/15] arm,fs/resctrl: ARM MPAM memory-level MSC and CPU-less NUMA nodes Fenghua Yu
                   ` (11 preceding siblings ...)
  2026-11-09 21:24 ` [PATCH RFC v3 12/15] Documentation: resctrl: document mbm_NODE_assignments Fenghua Yu
@ 2026-11-09 21:24 ` Fenghua Yu
  2026-11-09 21:24 ` [PATCH RFC v3 14/15] Documentation: resctrl: document NUMA-node MBA domains and mon_NODE monitoring Fenghua Yu
  2026-11-09 21:24 ` [PATCH RFC v3 15/15] arm_mpam: Add KUnit test for CPU-less NUMA node affinity Fenghua Yu
  14 siblings, 0 replies; 16+ messages in thread
From: Fenghua Yu @ 2026-11-09 21:24 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Will Deacon,
	Dave Martin, 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, and can manage CPU-less memory nodes by falling
back to cpu_possible_mask.

The schemata line is MB for an L3-cache MSC and MB_NODE for a memory MSC,
in both legacy and native control_mode. info/MB/control_mode is legacy by
default.

Update the arm64 MPAM documentation to describe the L3-cache and memory
MSC paths for both the MB control and MBWU monitoring, 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 | 95 +++++++++++++++++++------------
 1 file changed, 58 insertions(+), 37 deletions(-)

diff --git a/Documentation/arch/arm64/mpam.rst b/Documentation/arch/arm64/mpam.rst
index 67fe515ed501..cbc4dd828027 100644
--- a/Documentation/arch/arm64/mpam.rst
+++ b/Documentation/arch/arm64/mpam.rst
@@ -44,48 +44,69 @@ 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.  ``info/MB/control_mode``
+  is ``legacy`` by default.  Legacy and native mode show the same schemata
+  line; which line that is depends 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 schemata line is
+  ``MB:`` in both legacy and native ``control_mode``.  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 schemata line is
+  ``MB_NODE:`` in both legacy and native ``control_mode``.  Each numeric
+  ID ``XX`` in that 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_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_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_NODE:`` 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	[flat|nested] 16+ messages in thread

* [PATCH RFC v3 14/15] Documentation: resctrl: document NUMA-node MBA domains and mon_NODE monitoring
  2026-11-09 21:24 [PATCH RFC v3 00/15] arm,fs/resctrl: ARM MPAM memory-level MSC and CPU-less NUMA nodes Fenghua Yu
                   ` (12 preceding siblings ...)
  2026-11-09 21:24 ` [PATCH RFC v3 13/15] Documentation: arm64: mpam: document memory-level MB control and NUMA nodes Fenghua Yu
@ 2026-11-09 21:24 ` Fenghua Yu
  2026-11-09 21:24 ` [PATCH RFC v3 15/15] arm_mpam: Add KUnit test for CPU-less NUMA node affinity Fenghua Yu
  14 siblings, 0 replies; 16+ messages in thread
From: Fenghua Yu @ 2026-11-09 21:24 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Will Deacon,
	Dave Martin, 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 schemata line is
MB_NODE and its identifiers are NUMA node ids rather than L3 cache ids.
Document mon_NODE_XX monitor directories and how to interpret MB and
MB_NODE lines.

Signed-off-by: Fenghua Yu <fenghuay@nvidia.com>
---
 Documentation/filesystems/resctrl.rst | 41 ++++++++++++++++++++++++---
 1 file changed, 37 insertions(+), 4 deletions(-)

diff --git a/Documentation/filesystems/resctrl.rst b/Documentation/filesystems/resctrl.rst
index 6ead819a7a2a..b7ffedc02b41 100644
--- a/Documentation/filesystems/resctrl.rst
+++ b/Documentation/filesystems/resctrl.rst
@@ -285,6 +285,10 @@ 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 are that control's
+domain ids. The same ids are used by the matching monitor directories
+under ``mon_data`` (``mon_L3_XX`` or ``mon_NODE_XX``).
+
 For example, below shows MB, MB_MIN, and MB_MAX as controls for the "MB" resource.
 
 	 info/
@@ -816,6 +820,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_NODE:" line of
+	"schemata". 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",
@@ -1152,19 +1164,40 @@ 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 schemata line is
+"MB_NODE:" in both legacy and native control_mode. info/MB/control_mode
+is legacy by default. The identifiers are NUMA node ids::
+
+	MB_NODE:<node_id0>=bandwidth0;<node_id1>=bandwidth1;...
+
+Platforms whose MB control is a cache MSC, and which have no CPU-less
+memory MSC, keep the "MB:" line in both modes.
+
+Example on a system where MB domains are NUMA nodes::
+
+	# cat schemata
+	MB_NODE: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".
+
 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 MB domains are NUMA nodes::
+
+	MB_NODE:<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	[flat|nested] 16+ messages in thread

* [PATCH RFC v3 15/15] arm_mpam: Add KUnit test for CPU-less NUMA node affinity
  2026-11-09 21:24 [PATCH RFC v3 00/15] arm,fs/resctrl: ARM MPAM memory-level MSC and CPU-less NUMA nodes Fenghua Yu
                   ` (13 preceding siblings ...)
  2026-11-09 21:24 ` [PATCH RFC v3 14/15] Documentation: resctrl: document NUMA-node MBA domains and mon_NODE monitoring Fenghua Yu
@ 2026-11-09 21:24 ` Fenghua Yu
  14 siblings, 0 replies; 16+ messages in thread
From: Fenghua Yu @ 2026-11-09 21:24 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Will Deacon,
	Dave Martin, 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	[flat|nested] 16+ messages in thread

end of thread, other threads:[~2026-11-09 21:25 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-09 21:24 [PATCH RFC v3 00/15] arm,fs/resctrl: ARM MPAM memory-level MSC and CPU-less NUMA nodes Fenghua Yu
2026-11-09 21:24 ` [PATCH RFC v3 01/15] resctrl: De-hardcode L3 monitor infrastructure Fenghua Yu
2026-11-09 21:24 ` [PATCH RFC v3 02/15] resctrl: Expose MBA MBM counter assignment sysfs Fenghua Yu
2026-11-09 21:24 ` [PATCH RFC v3 03/15] resctrl: name node-scoped monitor domains mon_NODE_<id> Fenghua Yu
2026-11-09 21:24 ` [PATCH RFC v3 04/15] resctrl: Add node-scope MBM total event Fenghua Yu
2026-11-09 21:24 ` [PATCH RFC v3 05/15] resctrl: Make MBM paths resource-aware Fenghua Yu
2026-11-09 21:24 ` [PATCH RFC v3 06/15] arm_mpam: Support memory-level MSCs and ABMC per class Fenghua Yu
2026-11-09 21:24 ` [PATCH RFC v3 07/15] arm_mpam: Refine L3 topology and class selection Fenghua Yu
2026-11-09 21:24 ` [PATCH RFC v3 08/15] arm_mpam: Include all MSC components during domain setup Fenghua Yu
2026-11-09 21:24 ` [PATCH RFC v3 09/15] fs/resctrl: Take memory hotplug lock whenever taking CPU hotplug lock Fenghua Yu
2026-11-09 21:24 ` [PATCH RFC v3 10/15] arm_mpam: Handle CPU-less numa nodes Fenghua Yu
2026-11-09 21:24 ` [PATCH RFC v3 11/15] arm_mpam: resctrl: Add NUMA node notifier for domain online/offline Fenghua Yu
2026-11-09 21:24 ` [PATCH RFC v3 12/15] Documentation: resctrl: document mbm_NODE_assignments Fenghua Yu
2026-11-09 21:24 ` [PATCH RFC v3 13/15] Documentation: arm64: mpam: document memory-level MB control and NUMA nodes Fenghua Yu
2026-11-09 21:24 ` [PATCH RFC v3 14/15] Documentation: resctrl: document NUMA-node MBA domains and mon_NODE monitoring Fenghua Yu
2026-11-09 21:24 ` [PATCH RFC v3 15/15] arm_mpam: Add KUnit test for CPU-less NUMA node affinity Fenghua Yu

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®