Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH RFC 00/23] resctrl: MBA control emulation and ARM MPAM MB_NODE support
@ 2026-07-16 21:02 Fenghua Yu
  2026-07-16 21:02 ` [PATCH 01/23] resctrl: Fix ownership of resource_schemata control subdirectories Fenghua Yu
                   ` (22 more replies)
  0 siblings, 23 replies; 24+ messages in thread
From: Fenghua Yu @ 2026-07-16 21:02 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
	Shaopeng Tan, Chen Yu, Babu Moger, Drew Fustini, Vikram Sethi,
	Shanker Donthineni, Newton Liu, Gavin Shan
  Cc: linux-kernel, linux-arm-kernel, Fenghua Yu

This series enables MBA bandwidth control emulation on ARM MPAM when the
default MB control is disabled, using a node-scoped MB_NODE control as
the native backend.  It extends memory-bandwidth monitoring and
control to memory-level MSCs or CPU-less NUMA nodes, and adds
documentation and tests.

Background

On x86, MBA bandwidth control is typically associated with the L3 cache:
schemata lines use L3 cache ids and monitor domains appear as mon_L3_XX.
On some ARM MPAM systems the MBA control is backed by a memory-level MSC
rather than the L3 cache MSC.  A memory-level MSC is represented as a
NUMA node, and that node commonly has no CPUs of its own (a memory-only
node that still participates in bandwidth control).  For such a resource
the control scope is NODE, schemata identifiers are NUMA node ids,
monitor domains are named mon_NODE_XX, and a node-scoped MB_NODE control
may be the only control with usable bandwidth hardware.

Because the memory-level MSC lives on a CPU-less NUMA node, the driver
must handle nodes with no local CPUs.  mpam_ris_get_affinity() derives
affinity from the component's NUMA node id, so a memory-only node would
otherwise end up with an empty CPU mask and never be registered.  When
the derived mask is empty, the driver falls back to cpu_possible_mask
(masked by MSC accessibility) so the node still gets a resctrl domain.
The borrowed affinity is kept out of class->affinity: those CPUs already
contribute through CPU-ful nodes in the class, and subtracting the
borrowed mask on teardown would remove CPUs that other live nodes still
depend on.  Domain setup iterates all MSC components per CPU (keyed by
component), so each component — including these CPU-less memory nodes —
gets its own control and monitor domain.

Examples of NODE and emulation interfaces on MPAM.

**MB disabled, MB_NODE enabled** (``MB`` emulated by ``MB_NODE``)::

        info/MB/resource_schemata/
        ├── mode			  # [legacy] native
        └── MB/
            ├── scope                     # NODE
            ├── status                    # disabled
            └── MB_NODE/
                ├── scope                 # NODE
                └── status                # enabled
schemata:   MB_NODE and MB keeps same values
        MB_NODE:0=100;1=100;2=100;10=100;18=100;26=100;34=100;35=100
             MB: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  mon_NODE_00  mon_NODE_02  mon_NODE_18  mon_NODE_34
mon_L3_02  mon_NODE_01  mon_NODE_10  mon_NODE_26  mon_NODE_35

mon_data/mon_L3_01/llc_occupancy
mon_data/mon_NODE_01/mbm_total_bytes

Switch to native by writing to "mode":

        info/MB/resource_schemata/
        ├── mode			  # legacy [native]
        ├── MB/
        │   └── scope                     # NODE
        │   └── status                    # disabled
        └── MB_NODE/			  # MB_NODE is moved to here
                ├── scope                 # NODE
                └── status                # enabled

schemata:   Only show MB_NODE. No "MB:" is emulated.
        MB_NODE:0=100;1=100;2=100;10=100;18=100;26=100;34=100;35=100
             L3:1=ffff;2=ffff

mon_data/
mon_L3_01  mon_NODE_00  mon_NODE_02  mon_NODE_18  mon_NODE_34
mon_L3_02  mon_NODE_01  mon_NODE_10  mon_NODE_26  mon_NODE_35

mon_data/mon_L3_01/llc_occupancy
mon_data/mon_NODE_01/mbm_total_bytes

**MB enabled, MB_NODE enabled**::

Same for legacy and native:

        info/MB/resource_schemata/
        ├── mode			  # [legacy] native
        ├── MB/
        │   ├── scope                     # L3
        │   └── status                    # enabled
        └── MB_NODE/
            ├── scope                     # NODE
            └── status                    # enabled
schemata:
        MB_NODE:0=100;1=100;2=100;10=100;18=100;26=100;34=100;35=100
             MB:1=100;2=100
             L3:1=ffff;2=ffff

mon_data/
mon_L3_01  mon_NODE_00  mon_NODE_02  mon_NODE_18  mon_NODE_34
mon_L3_02  mon_NODE_01  mon_NODE_10  mon_NODE_26  mon_NODE_35

mon_data/mon_L3_01/llc_occupancy
mon_data/mon_L3_01/mbm_total_bytes
mon_data/mon_NODE_01/mbm_total_bytes

The patches can be compiled but not tested on x86.

The 23 patches are grouped as follows:

1. Fixes for previous issues (patches 1–2)

   Correct resource_schemata control subdirectory ownership on non-root
   resctrl mounts, and fix a NULL address access in the ARM MPAM driver.

2. resctrl control emulation (patches 3–8)

   Introduce the MBA emulation framework in the resctrl core: a writable
   legacy/native mode under info/MB/resource_schemata/, per-control status
   and nested schemata layout, schemata mirroring for controls without MBW
   hardware, and subdir rebuild when the mode changes.  Patch 8 documents
   the user-visible behaviour in Documentation/filesystems/resctrl.rst.

   This group and group 4 are trying to implement the concept in
   discussion [1].

   The main purpose of this group is to make CPU-less NUMA Node patches
   work.

   This implementation is only limited to MPAM CPU-less NUMA Node
   emulation. It needs to be enhanced to make another other emulation
   work, X86 memory region or RISC-V emulation etc.

   It's totally fine for me to either replace this implementation with
   another implementation or continue to enhance this implementition
   for control emulations.

3. Memory bandwidth monitoring and control on memory-level MSCs
   (patches 9–17)

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

   On the ARM MPAM side, add memory-level MSC and ABMC support, refine
   L3 topology and class selection, refactor domain setup to iterate all
   MSC components per CPU (keyed by component), and handle CPU-less NUMA
   memory nodes that have no local CPUs but still participate in MPAM
   bandwidth control.

4. MB_NODE emulates MB (patch 18)

   Wire MB_NODE as the native backend that emulates a disabled MB control
   in the ARM MPAM driver.

5. Documentation (patches 19–21)

   Document memory-level MB control and NUMA nodes in the arm64 MPAM
   guide (patch 19), NODE-scoped MBA domains and mon_NODE_* monitoring
   in the resctrl documentation (patch 20), and an MB_NODE emulation
   example on ARM MPAM (patch 21).

6. Tests (patches 22–23)

   Add a KUnit test for CPU-less NUMA node affinity handling and a
   kselftest that exercises MB emulation mode switching, resource_schemata
   hierarchy, and schemata mirroring on ARM MPAM.

To apply the patches, on top of [2],

Testing

  # KUnit (CPU-less affinity)
  ./tools/testing/kunit/kunit.py run test_mpam_devices

  # kselftest (MB emulation; requires ARM MPAM with disabled MB control)
  cd tools/testing/selftests/resctrl
  ./resctrl_tests -a mb_emulation_test

The branch: https://github.com/fyu1/linux/  emul.cpu_less.rfc/
which is on top of [2].

[1] [RFC] mpam,x86,fs/resctrl: Generic schema description Proof of Concept
https://lore.kernel.org/lkml/cd96bda7-675d-451e-b6d9-9fe4a405edbe@intel.com/
[2] Reinette's resctrl control branch: git://git.kernel.org/pub/scm/linux/kernel/git/reinette/linux.git branch resctrl/controls_rfc_v1


Fenghua Yu (23):
  resctrl: Fix ownership of resource_schemata control subdirectories
  arm_mpam: Fix NULL address access issue
  resctrl: Expose MBA resource_schemata mode sysfs
  resctrl: Expose per-control status in resource_schemata
  resctrl: Add nested resource_schemata support for emulated controls
  resctrl: Mirror schemata for controls without MBW hardware
  resctrl: Rebuild resource_schemata subdirs on MBA mode change
  Documentation: resctrl: document MBA control emulation
  resctrl: De-hardcode L3 monitor infrastructure
  resctrl: Expose MBA MBM counter assignment sysfs
  resctrl: name node-scoped monitor domains mon_NODE_<id>
  resctrl: Add node-scope MBM total event
  resctrl: Make MBM paths resource-aware
  arm_mpam: Support memory-level MSCs and ABMC per class
  arm_mpam: Refine L3 topology and class selection
  arm_mpam: Include all MSC components during domain setup
  arm_mpam: Handle CPU-less numa nodes
  arm_mpam: Emulate MB control with node-scoped MB_NODE control
  Documentation: arm64: mpam: document memory-level MB control and NUMA nodes
  Documentation: resctrl: document NODE-scoped MBA domains and mon_NODE monitoring
  Documentation: resctrl: document MB_NODE emulation example on ARM MPAM
  arm_mpam: Add KUnit test for CPU-less NUMA node affinity
  selftests/resctrl: Add MB emulation test for ARM MPAM

 Documentation/arch/arm64/mpam.rst             | 105 ++--
 Documentation/filesystems/resctrl.rst         | 184 ++++++-
 arch/x86/kernel/cpu/resctrl/core.c            |   2 +
 drivers/resctrl/mpam_devices.c                |  59 +-
 drivers/resctrl/mpam_internal.h               |   8 +
 drivers/resctrl/mpam_resctrl.c                | 488 ++++++++++++-----
 drivers/resctrl/test_mpam_devices.c           |  67 +++
 fs/resctrl/ctrlmondata.c                      |  99 +++-
 fs/resctrl/internal.h                         |   9 +-
 fs/resctrl/monitor.c                          | 225 +++++---
 fs/resctrl/rdtgroup.c                         | 507 +++++++++++++++---
 include/linux/resctrl.h                       |  62 ++-
 include/linux/resctrl_types.h                 |  15 +-
 tools/testing/selftests/resctrl/config        |   1 +
 .../selftests/resctrl/mb_emulation_test.c     | 460 ++++++++++++++++
 tools/testing/selftests/resctrl/resctrl.h     |   1 +
 .../testing/selftests/resctrl/resctrl_tests.c |   1 +
 17 files changed, 1942 insertions(+), 351 deletions(-)
 create mode 100644 tools/testing/selftests/resctrl/mb_emulation_test.c

-- 
2.43.0


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

* [PATCH 01/23] resctrl: Fix ownership of resource_schemata control subdirectories
  2026-07-16 21:02 [PATCH RFC 00/23] resctrl: MBA control emulation and ARM MPAM MB_NODE support Fenghua Yu
@ 2026-07-16 21:02 ` Fenghua Yu
  2026-07-16 21:02 ` [PATCH 02/23] arm_mpam: Fix NULL address access issue Fenghua Yu
                   ` (21 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Fenghua Yu @ 2026-07-16 21:02 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
	Shaopeng Tan, Chen Yu, Babu Moger, Drew Fustini, Vikram Sethi,
	Shanker Donthineni, Newton Liu, Gavin Shan
  Cc: linux-kernel, linux-arm-kernel, Fenghua Yu

resctrl_mkdir_schemata_dir() creates a control subdirectory (kn_ctrl)
for each control under resource_schemata, but applied ownership to the
parent resource_schemata directory (kn_subdir) instead of the newly
created subdirectory.

rdtgroup_kn_set_ugid() is a no-op when both uid and gid are root, so on
the common root mount this is harmless. On a non-root resctrl mount,
however, each control subdirectory keeps root:root ownership instead of
inheriting the mounting user's credentials.

Apply the ownership to kn_ctrl so each control subdirectory gets the
correct uid/gid.

On error, resctrl_mkdir_schemata_dir() intentionally does not unwind
partially created nodes (see the comment above the function). The sole
caller, rdtgroup_create_info_dir(), removes the whole info subtree via
kernfs_remove(kn_info), which recursively frees resource_schemata and
any control subdirectories already created.

Signed-off-by: Fenghua Yu <fenghuay@nvidia.com>
---
 fs/resctrl/rdtgroup.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/fs/resctrl/rdtgroup.c b/fs/resctrl/rdtgroup.c
index 5ccd09b16e0f..2abb7fda6091 100644
--- a/fs/resctrl/rdtgroup.c
+++ b/fs/resctrl/rdtgroup.c
@@ -2728,7 +2728,7 @@ static int resctrl_mkdir_schemata_dir(struct kernfs_node *kn,
 		if (IS_ERR(kn_ctrl))
 			return PTR_ERR(kn_ctrl);
 
-		ret = rdtgroup_kn_set_ugid(kn_subdir);
+		ret = rdtgroup_kn_set_ugid(kn_ctrl);
 		if (ret)
 			return ret;
 
-- 
2.43.0



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

* [PATCH 02/23] arm_mpam: Fix NULL address access issue
  2026-07-16 21:02 [PATCH RFC 00/23] resctrl: MBA control emulation and ARM MPAM MB_NODE support Fenghua Yu
  2026-07-16 21:02 ` [PATCH 01/23] resctrl: Fix ownership of resource_schemata control subdirectories Fenghua Yu
@ 2026-07-16 21:02 ` Fenghua Yu
  2026-07-16 21:02 ` [PATCH 03/23] resctrl: Expose MBA resource_schemata mode sysfs Fenghua Yu
                   ` (20 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Fenghua Yu @ 2026-07-16 21:02 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
	Shaopeng Tan, Chen Yu, Babu Moger, Drew Fustini, Vikram Sethi,
	Shanker Donthineni, Newton Liu, Gavin Shan
  Cc: linux-kernel, linux-arm-kernel, Fenghua Yu

mpam_resctrl_setup() initializes each resource's mon_domains list but
not controls. mpam_resctrl_controls[] is statically allocated, so the
controls list heads are zero-initialized rather than empty lists.
Generic resctrl walks controls with for_each_resource_ctrl(), which
dereferences through the list head's next pointer and can hit NULL on
resources that have no controls yet (for example dummy resources whose
class is unset).

Match the existing mon_domains initialization and initialize controls
with INIT_LIST_HEAD() before any resctrl setup path can walk the list.
x86 resctrl uses LIST_HEAD_INIT() at compile time for the same reason.

Signed-off-by: Fenghua Yu <fenghuay@nvidia.com>
---
 drivers/resctrl/mpam_resctrl.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/resctrl/mpam_resctrl.c b/drivers/resctrl/mpam_resctrl.c
index 68b94008d38e..e2cb3384820a 100644
--- a/drivers/resctrl/mpam_resctrl.c
+++ b/drivers/resctrl/mpam_resctrl.c
@@ -1942,6 +1942,7 @@ int mpam_resctrl_setup(void)
 
 	cpus_read_lock();
 	for_each_mpam_resctrl_control(res, rid) {
+		INIT_LIST_HEAD(&res->resctrl_res.controls);
 		INIT_LIST_HEAD_RCU(&res->resctrl_res.mon_domains);
 		res->resctrl_res.rid = rid;
 	}
-- 
2.43.0



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

* [PATCH 03/23] resctrl: Expose MBA resource_schemata mode sysfs
  2026-07-16 21:02 [PATCH RFC 00/23] resctrl: MBA control emulation and ARM MPAM MB_NODE support Fenghua Yu
  2026-07-16 21:02 ` [PATCH 01/23] resctrl: Fix ownership of resource_schemata control subdirectories Fenghua Yu
  2026-07-16 21:02 ` [PATCH 02/23] arm_mpam: Fix NULL address access issue Fenghua Yu
@ 2026-07-16 21:02 ` Fenghua Yu
  2026-07-16 21:02 ` [PATCH 04/23] resctrl: Expose per-control status in resource_schemata Fenghua Yu
                   ` (19 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Fenghua Yu @ 2026-07-16 21:02 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
	Shaopeng Tan, Chen Yu, Babu Moger, Drew Fustini, Vikram Sethi,
	Shanker Donthineni, Newton Liu, Gavin Shan
  Cc: linux-kernel, linux-arm-kernel, Fenghua Yu

Node-scoped MBA on MPAM needs a way to distinguish native memory-side
controls from legacy L3-shaped MB emulation. Track the selected emulate
mode on rdt_resource and expose it as
info/<resource>/resource_schemata/mode ("native" or "legacy") when the
architecture enables emulation.

The mode file is only created when rdt_resource::mode is non-zero
(RESCTRL_CTRL_LEGACY or RESCTRL_CTRL_NATIVE). It defaults to
RESCTRL_CTRL_MODE_NONE, so resources whose architecture does not support
control emulation get no mode file and are unaffected. Architecture
backends that support emulation set the initial mode when they create
their controls; on MPAM this is wired up together with the node-scoped
MB_NODE control in a later patch, so this commit only adds the (dormant)
generic mechanism.

The mode file is added read-only here: switching the mode at runtime
requires rebuilding the resource_schemata layout to match the new mode,
so the writable interface is added together with that rebuild logic in a
later patch. Keeping the file read-only until then avoids exposing a
writable-but-no-op interface.

Store rdt_resource_final in the resource_schemata directory priv so the
mode file can resolve the backing resource without dereferencing NULL.

Signed-off-by: Fenghua Yu <fenghuay@nvidia.com>
---
 fs/resctrl/rdtgroup.c   | 77 ++++++++++++++++++++++++++++++++++++++++-
 include/linux/resctrl.h | 17 +++++++++
 2 files changed, 93 insertions(+), 1 deletion(-)

diff --git a/fs/resctrl/rdtgroup.c b/fs/resctrl/rdtgroup.c
index 2abb7fda6091..6b1f24c6a1f2 100644
--- a/fs/resctrl/rdtgroup.c
+++ b/fs/resctrl/rdtgroup.c
@@ -2696,6 +2696,74 @@ static unsigned long fflags_from_resource(struct rdt_resource *r)
 	return WARN_ON_ONCE(1);
 }
 
+static int resctrl_ctrl_mb_mode_show(struct kernfs_open_file *of,
+				     struct seq_file *seq, void *v)
+{
+	struct rdt_resource_final *f = rdt_kn_parent_priv(of->kn);
+	struct rdt_resource *r = f->res;
+
+	guard(mutex)(&rdtgroup_mutex);
+
+	switch (r->mode) {
+	case RESCTRL_CTRL_LEGACY:
+		seq_puts(seq, "[legacy] native\n");
+		break;
+	case RESCTRL_CTRL_NATIVE:
+		seq_puts(seq, "legacy [native]\n");
+		break;
+	default:
+		WARN_ONCE(1, "%s: unexpected MB control mode %d\n",
+			  f->name, r->mode);
+		seq_puts(seq, "legacy native\n");
+		break;
+	}
+
+	return 0;
+}
+
+static struct rftype resctrl_ctrl_mb_files[] = {
+	{
+		.name		= "mode",
+		.mode		= 0444,
+		.kf_ops		= &rdtgroup_kf_single_ops,
+		.seq_show	= resctrl_ctrl_mb_mode_show,
+		/*
+		 * Directory-level file, not per-control: fflags is only a
+		 * presence flag here, not the BIT(ctrl->type) type filter used
+		 * by resctrl_add_ctrl_files().
+		 */
+		.fflags		= 1,
+	}
+};
+
+static int resctrl_ctrl_add_files(struct kernfs_node *kn)
+{
+	struct rftype *rfts, *rft;
+	int ret, len;
+
+	rfts = resctrl_ctrl_mb_files;
+	len = ARRAY_SIZE(resctrl_ctrl_mb_files);
+
+	lockdep_assert_held(&rdtgroup_mutex);
+
+	for (rft = rfts; rft < rfts + len; rft++) {
+		if (rft->fflags) {
+			ret = rdtgroup_add_file(kn, rft);
+			if (ret)
+				goto error;
+		}
+	}
+
+	return 0;
+error:
+	pr_warn("Failed to add %s, err=%d\n", rft->name, ret);
+	while (--rft >= rfts) {
+		if (rft->fflags)
+			kernfs_remove_by_name(kn, rft->name);
+	}
+	return ret;
+}
+
 /*
  * No need to cleanup on exit - caller calls the recursive kernfs_remove()
  * on failure.
@@ -2704,11 +2772,12 @@ static int resctrl_mkdir_schemata_dir(struct kernfs_node *kn,
 				      struct rdt_resource_final *f)
 {
 	struct kernfs_node *kn_subdir, *kn_ctrl;
+	struct rdt_resource *r = f->res;
 	struct resctrl_ctrl *ctrl;
 	char ctrl_full_name[20];
 	int ret;
 
-	kn_subdir = kernfs_create_dir(kn, "resource_schemata", kn->mode, NULL);
+	kn_subdir = kernfs_create_dir(kn, "resource_schemata", kn->mode, f);
 	if (IS_ERR(kn_subdir))
 		return PTR_ERR(kn_subdir);
 
@@ -2716,6 +2785,12 @@ static int resctrl_mkdir_schemata_dir(struct kernfs_node *kn,
 	if (ret)
 		return ret;
 
+	if (r->mode) {
+		ret = resctrl_ctrl_add_files(kn_subdir);
+		if (ret)
+			return ret;
+	}
+
 	for_each_resource_ctrl(ctrl, f->res) {
 		ret = snprintf(ctrl_full_name, sizeof(ctrl_full_name), "%s%s%s",
 			       f->name, resctrl_ctrl_is_default(ctrl) ? "" : "_",
diff --git a/include/linux/resctrl.h b/include/linux/resctrl.h
index 72fb7256270e..4fc41e269d0b 100644
--- a/include/linux/resctrl.h
+++ b/include/linux/resctrl.h
@@ -257,6 +257,16 @@ enum resctrl_ctrl_unit {
 	RESCTRL_CTRL_UNIT_GBPS,
 };
 
+enum resctrl_ctrl_mode {
+	/*
+	 * Default (zero) value: the resource does not support control
+	 * emulation, so no resource_schemata/mode file is created for it.
+	 */
+	RESCTRL_CTRL_MODE_NONE = 0,
+	RESCTRL_CTRL_LEGACY,
+	RESCTRL_CTRL_NATIVE,
+};
+
 /**
  * struct resctrl_membw - Memory bandwidth allocation related data
  * @min_bw:		Minimum memory bandwidth percentage user can request
@@ -399,6 +409,12 @@ struct resctrl_ctrl {
  *			different memory bandwidths
  * @cache_io_alloc_capable:True if portion of the cache can be configured
  *			   for I/O traffic.
+ * @mode:		Control emulation mode for this resource.
+ *			RESCTRL_CTRL_MODE_NONE if the resource does not support
+ *			emulation. "legacy": keep the legacy MB control,
+ *			emulating it with a native control when it has no MBW
+ *			hardware of its own. "native": expose native controls
+ *			directly with no emulation.
  * @controls:		List of controls of an alloc_capable resource
  */
 struct rdt_resource {
@@ -413,6 +429,7 @@ struct rdt_resource {
 	bool				bw_delay_linear;
 	enum membw_throttle_mode	bw_throttle_mode;
 	bool				cache_io_alloc_capable;
+	enum resctrl_ctrl_mode		mode;
 	struct list_head		controls;
 };
 
-- 
2.43.0



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

* [PATCH 04/23] resctrl: Expose per-control status in resource_schemata
  2026-07-16 21:02 [PATCH RFC 00/23] resctrl: MBA control emulation and ARM MPAM MB_NODE support Fenghua Yu
                   ` (2 preceding siblings ...)
  2026-07-16 21:02 ` [PATCH 03/23] resctrl: Expose MBA resource_schemata mode sysfs Fenghua Yu
@ 2026-07-16 21:02 ` Fenghua Yu
  2026-07-16 21:02 ` [PATCH 05/23] resctrl: Add nested resource_schemata support for emulated controls Fenghua Yu
                   ` (18 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Fenghua Yu @ 2026-07-16 21:02 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
	Shaopeng Tan, Chen Yu, Babu Moger, Drew Fustini, Vikram Sethi,
	Shanker Donthineni, Newton Liu, Gavin Shan
  Cc: linux-kernel, linux-arm-kernel, Fenghua Yu

Emulated MB controls can be individually present or absent depending on
what the underlying MSC advertises, but user space has no way to tell
which controls under resource_schemata are actually backed by hardware.

Track this per control and expose it as a read-only "status" file for
scalar controls, reporting "enabled" or "disabled". The state is stored
as resctrl_membw::no_mbw_hw, which defaults to false so every control is
reported "enabled" (hardware-backed) unless an architecture explicitly
marks it as lacking MBW hardware of its own. This keeps the common case
correct on all architectures with no per-arch initialization code.

This commit only adds the generic field, the "status" file, and the
default (hardware-backed) reporting. No architecture writes no_mbw_hw
yet, so every control reports "enabled" until a backend sets it. On
MPAM the field is assigned for the emulated, hardware-less MB control in
a later patch that adds the node-scoped MB_NODE control.

Signed-off-by: Fenghua Yu <fenghuay@nvidia.com>
---
 fs/resctrl/rdtgroup.c   | 17 +++++++++++++++++
 include/linux/resctrl.h |  4 ++++
 2 files changed, 21 insertions(+)

diff --git a/fs/resctrl/rdtgroup.c b/fs/resctrl/rdtgroup.c
index 6b1f24c6a1f2..a3aad9b1ff6e 100644
--- a/fs/resctrl/rdtgroup.c
+++ b/fs/resctrl/rdtgroup.c
@@ -2318,6 +2318,16 @@ static int resctrl_ctrl_unit_show(struct kernfs_open_file *of,
 	return 0;
 }
 
+static int resctrl_ctrl_status_show(struct kernfs_open_file *of,
+				    struct seq_file *seq, void *v)
+{
+	struct resctrl_ctrl *ctrl = rdt_kn_parent_priv(of->kn);
+
+	seq_printf(seq, "%s\n", ctrl->membw.no_mbw_hw ? "disabled" : "enabled");
+
+	return 0;
+}
+
 static struct rftype ctrl_files[] = {
 	{
 		.name		= "scope",
@@ -2375,6 +2385,13 @@ static struct rftype ctrl_files[] = {
 		.seq_show	= resctrl_ctrl_unit_show,
 		.fflags		= BIT(RESCTRL_CTRL_SCALAR),
 	},
+	{
+		.name		= "status",
+		.mode		= 0444,
+		.kf_ops		= &rdtgroup_kf_single_ops,
+		.seq_show	= resctrl_ctrl_status_show,
+		.fflags		= BIT(RESCTRL_CTRL_SCALAR),
+	},
 };
 
 static int resctrl_add_ctrl_files(struct kernfs_node *kn, struct resctrl_ctrl *ctrl)
diff --git a/include/linux/resctrl.h b/include/linux/resctrl.h
index 4fc41e269d0b..5d8ea12ae16b 100644
--- a/include/linux/resctrl.h
+++ b/include/linux/resctrl.h
@@ -289,6 +289,9 @@ enum resctrl_ctrl_mode {
  *			"all" for a proportional schema. Base unit of an
  *			absolute control, for example "GBps".
  * @mba_sc:		True if MBA software controller(mba_sc) is enabled
+ * @no_mbw_hw:		True if the control has no MBW (mbw_max) hardware of its
+ *			own. Defaults to false (hardware-backed); architectures
+ *			set this for emulated controls that lack hardware.
  *
  * With a control value "C" written to the schemata file, min_bw <= C <= max_bw,
  * the amount of resource allocated by this control is:
@@ -303,6 +306,7 @@ struct resctrl_membw {
 	u32				scale;
 	enum resctrl_ctrl_unit		unit;
 	bool				mba_sc;
+	bool				no_mbw_hw;
 };
 
 enum resctrl_scope {
-- 
2.43.0



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

* [PATCH 05/23] resctrl: Add nested resource_schemata support for emulated controls
  2026-07-16 21:02 [PATCH RFC 00/23] resctrl: MBA control emulation and ARM MPAM MB_NODE support Fenghua Yu
                   ` (3 preceding siblings ...)
  2026-07-16 21:02 ` [PATCH 04/23] resctrl: Expose per-control status in resource_schemata Fenghua Yu
@ 2026-07-16 21:02 ` Fenghua Yu
  2026-07-16 21:02 ` [PATCH 06/23] resctrl: Mirror schemata for controls without MBW hardware Fenghua Yu
                   ` (17 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Fenghua Yu @ 2026-07-16 21:02 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
	Shaopeng Tan, Chen Yu, Babu Moger, Drew Fustini, Vikram Sethi,
	Shanker Donthineni, Newton Liu, Gavin Shan
  Cc: linux-kernel, linux-arm-kernel, Fenghua Yu

Some architectures expose an alternate, hardware-native control that
emulates a legacy control (for example a node-scoped bandwidth control
emulating the L3-shaped MB control). Represent this relationship with a
new resctrl_ctrl::emulated_by pointer stored on the emulated (default)
control, pointing at the control that emulates it, and add a
RESCTRL_CTRL_NAME_NODE control name for the node-scoped variant.

Build the resource_schemata tree accordingly: create the default control
directory first, then nest each emulating control beneath the default
control it emulates, while non-emulated controls remain directly under
resource_schemata. Creating the default directory up front makes the
layout independent of the order controls appear in the list.

Architecture backends set emulated_by during control init; until then the
nesting branch is never taken and all controls remain directly under
resource_schemata.

Factor the per-control directory creation into
resctrl_ctrl_create_subdir() and fix its error and ownership handling so
the newly created directory (not its parent) is validated and gets its
uid/gid applied.

Signed-off-by: Fenghua Yu <fenghuay@nvidia.com>
---
 arch/x86/kernel/cpu/resctrl/core.c |  2 +
 fs/resctrl/ctrlmondata.c           |  1 +
 fs/resctrl/rdtgroup.c              | 67 ++++++++++++++++++++++++------
 include/linux/resctrl.h            |  7 +++-
 4 files changed, 63 insertions(+), 14 deletions(-)

diff --git a/arch/x86/kernel/cpu/resctrl/core.c b/arch/x86/kernel/cpu/resctrl/core.c
index 07c6ebb7fc43..5575a16caeab 100644
--- a/arch/x86/kernel/cpu/resctrl/core.c
+++ b/arch/x86/kernel/cpu/resctrl/core.c
@@ -214,6 +214,8 @@ static __init bool __temporary_multiple_mba_intel_controls(struct rdt_resource *
 	case RESCTRL_CTRL_NAME_MAX:
 		hw_ctrl->msr_update = update_temporary_max;
 		break;
+	default:
+		break;
 	}
 
 	return true;
diff --git a/fs/resctrl/ctrlmondata.c b/fs/resctrl/ctrlmondata.c
index d95ab8ad36e2..1f832a838115 100644
--- a/fs/resctrl/ctrlmondata.c
+++ b/fs/resctrl/ctrlmondata.c
@@ -304,6 +304,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",
 };
 
 const char *resctrl_ctrl_name_str(enum resctrl_ctrl_name name)
diff --git a/fs/resctrl/rdtgroup.c b/fs/resctrl/rdtgroup.c
index a3aad9b1ff6e..3bec23728847 100644
--- a/fs/resctrl/rdtgroup.c
+++ b/fs/resctrl/rdtgroup.c
@@ -2781,6 +2781,26 @@ static int resctrl_ctrl_add_files(struct kernfs_node *kn)
 	return ret;
 }
 
+static int resctrl_ctrl_create_subdir(struct kernfs_node *kn_dir,
+				      struct rdt_resource_final *f,
+				      struct resctrl_ctrl *ctrl, struct kernfs_node **kn_ctrl)
+{
+	char ctrl_full_name[20];
+	int ret;
+
+	ret = snprintf(ctrl_full_name, sizeof(ctrl_full_name), "%s%s%s",
+		       f->name, resctrl_ctrl_is_default(ctrl) ? "" : "_",
+		       resctrl_ctrl_is_default(ctrl) ? "" : resctrl_ctrl_name_str(ctrl->name));
+	if (ret >= sizeof(ctrl_full_name))
+		return -ENOSPC;
+
+	*kn_ctrl = kernfs_create_dir(kn_dir, ctrl_full_name, kn_dir->mode, ctrl);
+	if (IS_ERR(*kn_ctrl))
+		return PTR_ERR(*kn_ctrl);
+
+	return rdtgroup_kn_set_ugid(*kn_ctrl);
+}
+
 /*
  * No need to cleanup on exit - caller calls the recursive kernfs_remove()
  * on failure.
@@ -2788,10 +2808,9 @@ static int resctrl_ctrl_add_files(struct kernfs_node *kn)
 static int resctrl_mkdir_schemata_dir(struct kernfs_node *kn,
 				      struct rdt_resource_final *f)
 {
-	struct kernfs_node *kn_subdir, *kn_ctrl;
+	struct kernfs_node *kn_subdir, *kn_ctrl, *kn_ctrl_def = NULL;
+	struct resctrl_ctrl *ctrl, *ctrl_def = NULL;
 	struct rdt_resource *r = f->res;
-	struct resctrl_ctrl *ctrl;
-	char ctrl_full_name[20];
 	int ret;
 
 	kn_subdir = kernfs_create_dir(kn, "resource_schemata", kn->mode, f);
@@ -2808,19 +2827,41 @@ static int resctrl_mkdir_schemata_dir(struct kernfs_node *kn,
 			return ret;
 	}
 
+	/*
+	 * Create the default control sub-dir first. Emulated controls are
+	 * nested underneath it and may be iterated before it, so it has to
+	 * exist before the rest of the controls are created.
+	 */
 	for_each_resource_ctrl(ctrl, f->res) {
-		ret = snprintf(ctrl_full_name, sizeof(ctrl_full_name), "%s%s%s",
-			       f->name, resctrl_ctrl_is_default(ctrl) ? "" : "_",
-			       resctrl_ctrl_is_default(ctrl) ? "" : resctrl_ctrl_name_str(ctrl->name));
-		if (ret >= sizeof(ctrl_full_name))
-			return -ENOSPC;
+		if (ctrl->name != RESCTRL_CTRL_NAME_DEF)
+			continue;
 
-		kn_ctrl = kernfs_create_dir(kn_subdir, ctrl_full_name, kn_subdir->mode,
-					    ctrl);
-		if (IS_ERR(kn_ctrl))
-			return PTR_ERR(kn_ctrl);
+		ret = resctrl_ctrl_create_subdir(kn_subdir, f, ctrl, &kn_ctrl_def);
+		if (ret)
+			return ret;
+		ret = resctrl_add_ctrl_files(kn_ctrl_def, ctrl);
+		if (ret)
+			return ret;
+		ctrl_def = ctrl;
+		break;
+	}
 
-		ret = rdtgroup_kn_set_ugid(kn_ctrl);
+	for_each_resource_ctrl(ctrl, f->res) {
+		if (ctrl->name == RESCTRL_CTRL_NAME_DEF)
+			continue;
+
+		/*
+		 * In legacy mode the emulating control is nested under the
+		 * default control it emulates (the default control's
+		 * emulated_by points at it). In native mode there is no
+		 * emulation, so every control sits directly under
+		 * resource_schemata.
+		 */
+		if (r->mode == RESCTRL_CTRL_LEGACY &&
+		    ctrl_def && ctrl_def->emulated_by == ctrl)
+			ret = resctrl_ctrl_create_subdir(kn_ctrl_def, f, ctrl, &kn_ctrl);
+		else
+			ret = resctrl_ctrl_create_subdir(kn_subdir, f, ctrl, &kn_ctrl);
 		if (ret)
 			return ret;
 
diff --git a/include/linux/resctrl.h b/include/linux/resctrl.h
index 5d8ea12ae16b..06482e766087 100644
--- a/include/linux/resctrl.h
+++ b/include/linux/resctrl.h
@@ -362,12 +362,14 @@ 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"
  */
 enum resctrl_ctrl_name {
 	RESCTRL_CTRL_NAME_DEF,
 	RESCTRL_CTRL_NAME_MIN,
 	RESCTRL_CTRL_NAME_MAX,
-	RESCTRL_CTRL_NAME_LAST = RESCTRL_CTRL_NAME_MAX
+	RESCTRL_CTRL_NAME_NODE,
+	RESCTRL_CTRL_NAME_LAST = RESCTRL_CTRL_NAME_NODE
 };
 
 /**
@@ -383,6 +385,8 @@ enum resctrl_ctrl_name {
  *		Specifically, "rdt_resource_final::name"_"resctrl_ctrl::name".
  *		For example, with resource name "MB" and control name "MAX" the
  *		schema entry will be "MB_MAX".
+ * @emulated_by: For an emulated control, points at the control that emulates
+ *		it; NULL if this control is not emulated.
  * @cache:	Cache allocation control properties.
  * @membw:	Bandwidth control properties.
  */
@@ -392,6 +396,7 @@ struct resctrl_ctrl {
 	struct list_head	domains;
 	enum resctrl_ctrl_type	type;
 	enum resctrl_ctrl_name	name;
+	struct resctrl_ctrl	*emulated_by;
 	union {
 		struct resctrl_cache	cache;
 		struct resctrl_membw	membw;
-- 
2.43.0



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

* [PATCH 06/23] resctrl: Mirror schemata for controls without MBW hardware
  2026-07-16 21:02 [PATCH RFC 00/23] resctrl: MBA control emulation and ARM MPAM MB_NODE support Fenghua Yu
                   ` (4 preceding siblings ...)
  2026-07-16 21:02 ` [PATCH 05/23] resctrl: Add nested resource_schemata support for emulated controls Fenghua Yu
@ 2026-07-16 21:02 ` Fenghua Yu
  2026-07-16 21:02 ` [PATCH 07/23] resctrl: Rebuild resource_schemata subdirs on MBA mode change Fenghua Yu
                   ` (16 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Fenghua Yu @ 2026-07-16 21:02 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
	Shaopeng Tan, Chen Yu, Babu Moger, Drew Fustini, Vikram Sethi,
	Shanker Donthineni, Newton Liu, Gavin Shan
  Cc: linux-kernel, linux-arm-kernel, Fenghua Yu

When an emulated control has no MBW hardware of its own, redirect
schemata reads and writes to the backing control so both lines show
and update the same value.

Signed-off-by: Fenghua Yu <fenghuay@nvidia.com>
---
 fs/resctrl/ctrlmondata.c | 93 ++++++++++++++++++++++++++++++++++++----
 fs/resctrl/rdtgroup.c    |  1 +
 include/linux/resctrl.h  |  6 +++
 3 files changed, 91 insertions(+), 9 deletions(-)

diff --git a/fs/resctrl/ctrlmondata.c b/fs/resctrl/ctrlmondata.c
index 1f832a838115..ee7823cf9493 100644
--- a/fs/resctrl/ctrlmondata.c
+++ b/fs/resctrl/ctrlmondata.c
@@ -28,6 +28,13 @@ struct rdt_parse_data {
 	u32			closid;
 	enum rdtgrp_mode	mode;
 	char			*buf;
+	/*
+	 * The control whose schemata line is being parsed, before any
+	 * redirection to a backing control. Used to distinguish a real
+	 * duplicate domain from the mirrored write of an emulated control and
+	 * the control that backs it.
+	 */
+	struct resctrl_ctrl	*line_ctrl;
 };
 
 typedef int (ctrlval_parser_t)(struct rdt_parse_data *data,
@@ -105,7 +112,7 @@ static int parse_bw(struct rdt_parse_data *data, struct rdt_resource_final *f,
 	u32 bw_val;
 
 	cfg = &d->staged_config[f->conf_type];
-	if (cfg->have_new_ctrl) {
+	if (cfg->have_new_ctrl && cfg->staged_ctrl == data->line_ctrl) {
 		rdt_last_cmd_printf("Duplicate domain %d\n", d->hdr.id);
 		return -EINVAL;
 	}
@@ -118,8 +125,18 @@ static int parse_bw(struct rdt_parse_data *data, struct rdt_resource_final *f,
 		return 0;
 	}
 
+	if (cfg->have_new_ctrl) {
+		if (cfg->new_ctrl != bw_val) {
+			rdt_last_cmd_printf("Conflicting values for domain %d\n",
+					    d->hdr.id);
+			return -EINVAL;
+		}
+		return 0;
+	}
+
 	cfg->new_ctrl = bw_val;
 	cfg->have_new_ctrl = true;
+	cfg->staged_ctrl = data->line_ctrl;
 
 	return 0;
 }
@@ -185,7 +202,7 @@ static int parse_cbm(struct rdt_parse_data *data, struct rdt_resource_final *f,
 	u32 cbm_val;
 
 	cfg = &d->staged_config[f->conf_type];
-	if (cfg->have_new_ctrl) {
+	if (cfg->have_new_ctrl && cfg->staged_ctrl == data->line_ctrl) {
 		rdt_last_cmd_printf("Duplicate domain %d\n", d->hdr.id);
 		return -EINVAL;
 	}
@@ -228,6 +245,7 @@ static int parse_cbm(struct rdt_parse_data *data, struct rdt_resource_final *f,
 
 	cfg->new_ctrl = cbm_val;
 	cfg->have_new_ctrl = true;
+	cfg->staged_ctrl = data->line_ctrl;
 
 	return 0;
 }
@@ -238,11 +256,50 @@ static int parse_cbm(struct rdt_parse_data *data, struct rdt_resource_final *f,
  * separated by ";". The "id" is in decimal, and must match one of
  * the "id"s for this resource.
  */
+/*
+ * A control can be emulated by another control (for example the legacy,
+ * resource-wide MB control emulated by a node-scoped bandwidth control). When
+ * the control has no MBW hardware of its own (membw.no_mbw_hw), reads and writes
+ * are redirected to the control that emulates it (emulated_by) so both schemata
+ * lines show and update the same value.
+ *
+ * Emulation is only performed in legacy mode. In native mode
+ * (RESCTRL_CTRL_NATIVE) a control without hardware is left as-is, so a
+ * disabled control is not emulated by another control.
+ */
+static struct resctrl_ctrl *resctrl_ctrl_backing(struct rdt_resource *r,
+						 struct resctrl_ctrl *ctrl)
+{
+	if (r->mode == RESCTRL_CTRL_LEGACY &&
+	    ctrl->type == RESCTRL_CTRL_SCALAR &&
+	    ctrl->emulated_by && ctrl->membw.no_mbw_hw)
+		return ctrl->emulated_by;
+
+	return ctrl;
+}
+
+/*
+ * A control emulated by another control has no MBW hardware of its own, so it
+ * only has a schemata line while emulation is active. Emulation is performed
+ * in legacy mode only, so in native mode such a control is hidden from
+ * schemata rather than showing an empty or meaningless line.
+ */
+static bool resctrl_ctrl_schemata_hidden(struct rdt_resource *r,
+					 struct resctrl_ctrl *ctrl)
+{
+	if (ctrl->type != RESCTRL_CTRL_SCALAR ||
+	    !ctrl->emulated_by || !ctrl->membw.no_mbw_hw)
+		return false;
+
+	return r->mode != RESCTRL_CTRL_LEGACY;
+}
+
 static int parse_line(char *line, struct rdt_resource_final *f,
 		      struct resctrl_ctrl *ctrl, struct rdtgroup *rdtgrp)
 {
 	enum resctrl_conf_type t = f->conf_type;
 	ctrlval_parser_t *parse_ctrlval = NULL;
+	struct resctrl_ctrl *line_ctrl = ctrl;
 	struct resctrl_staged_config *cfg;
 	struct rdt_resource *r = f->res;
 	struct rdt_parse_data data;
@@ -253,6 +310,9 @@ static int parse_line(char *line, struct rdt_resource_final *f,
 	/* Walking r->domains, ensure it can't race with cpuhp */
 	lockdep_assert_cpus_held();
 
+	/* A control without MBW hardware mirrors the control that emulates it. */
+	ctrl = resctrl_ctrl_backing(r, ctrl);
+
 	parse_ctrlval = resctrl_ctrl_priv_all[ctrl->type].parser;
 
 	if (rdtgrp->mode == RDT_MODE_PSEUDO_LOCKSETUP &&
@@ -276,6 +336,7 @@ static int parse_line(char *line, struct rdt_resource_final *f,
 			data.buf = dom;
 			data.closid = rdtgrp->closid;
 			data.mode = rdtgrp->mode;
+			data.line_ctrl = line_ctrl;
 			if (parse_ctrlval(&data, f, d, ctrl))
 				return -EINVAL;
 			if (rdtgrp->mode ==  RDT_MODE_PSEUDO_LOCKSETUP) {
@@ -418,7 +479,7 @@ static int rdtgroup_parse_ctrl(char *ctrlname, char *tok,
 	list_for_each_entry(f, &rdt_resource_final_all, list) {
 		if (!strcmp(resname, f->name) && rdtgrp->closid < f->num_closid) {
 			ctrl = resctrl_resource_ctrl_get(f->res, ctrlname);
-			if (ctrl)
+			if (ctrl && !resctrl_ctrl_schemata_hidden(f->res, ctrl))
 				return parse_line(tok, f, ctrl, rdtgrp);
 			else
 				break;
@@ -511,6 +572,7 @@ static void show_doms(struct seq_file *s, struct rdt_resource_final *f,
 		      bool print_ctrl, int closid, struct resctrl_ctrl *ctrl)
 {
 	struct rdt_resource *r = f->res;
+	struct resctrl_ctrl *vctrl;
 	struct rdt_ctrl_domain *dom;
 	bool sep = false;
 	u32 ctrl_val;
@@ -523,17 +585,23 @@ static void show_doms(struct seq_file *s, struct rdt_resource_final *f,
 				resctrl_ctrl_is_default(ctrl) ? "" : "_",
 				resctrl_ctrl_is_default(ctrl) ?
 				 "" : resctrl_ctrl_name_str(ctrl->name));
-	list_for_each_entry(dom, &ctrl->domains, hdr.list) {
+
+	/*
+	 * A control without MBW hardware has no values of its own; show the
+	 * values of the control that emulates it so both schemata lines match.
+	 */
+	vctrl = resctrl_ctrl_backing(r, ctrl);
+	list_for_each_entry(dom, &vctrl->domains, hdr.list) {
 		if (sep)
 			seq_puts(s, ";");
 
-		if (is_mba_sc(r, ctrl))
+		if (is_mba_sc(r, vctrl))
 			ctrl_val = dom->mbps_val[closid];
 		else
-			ctrl_val = resctrl_arch_get_config(r, ctrl, dom, closid,
+			ctrl_val = resctrl_arch_get_config(r, vctrl, dom, closid,
 							   f->conf_type);
 
-		seq_printf(s, resctrl_ctrl_priv_all[ctrl->type].fmt_str,
+		seq_printf(s, resctrl_ctrl_priv_all[vctrl->type].fmt_str,
 			   dom->hdr.id, ctrl_val);
 		sep = true;
 	}
@@ -553,11 +621,14 @@ int rdtgroup_schemata_show(struct kernfs_open_file *of,
 	if (rdtgrp) {
 		if (rdtgrp->mode == RDT_MODE_PSEUDO_LOCKSETUP) {
 			list_for_each_entry(f, &rdt_resource_final_all, list) {
-				for_each_resource_ctrl(ctrl, f->res)
+				for_each_resource_ctrl(ctrl, f->res) {
+					if (resctrl_ctrl_schemata_hidden(f->res, ctrl))
+						continue;
 					seq_printf(s, "%s%s%s:uninitialized\n", f->name,
 						   resctrl_ctrl_is_default(ctrl) ? "" : "_",
 						   resctrl_ctrl_is_default(ctrl) ?
 						    "" : resctrl_ctrl_name_str(ctrl->name));
+				}
 			}
 		} else if (rdtgrp->mode == RDT_MODE_PSEUDO_LOCKED) {
 			if (!rdtgrp->plr->d) {
@@ -574,8 +645,11 @@ int rdtgroup_schemata_show(struct kernfs_open_file *of,
 			list_for_each_entry(f, &rdt_resource_final_all, list) {
 				if (closid >= f->num_closid)
 					continue;
-				for_each_resource_ctrl(ctrl, f->res)
+				for_each_resource_ctrl(ctrl, f->res) {
+					if (resctrl_ctrl_schemata_hidden(f->res, ctrl))
+						continue;
 					show_doms(s, f, true, closid, ctrl);
+				}
 			}
 		}
 	} else {
@@ -1147,6 +1221,7 @@ static int resctrl_io_alloc_parse_line(char *line, struct rdt_resource_final *f,
 			data.buf = dom;
 			data.mode = RDT_MODE_SHAREABLE;
 			data.closid = closid;
+			data.line_ctrl = ctrl;
 			if (parse_cbm(&data, f, d, ctrl))
 				return -EINVAL;
 			/*
diff --git a/fs/resctrl/rdtgroup.c b/fs/resctrl/rdtgroup.c
index 3bec23728847..a34c7ed8f874 100644
--- a/fs/resctrl/rdtgroup.c
+++ b/fs/resctrl/rdtgroup.c
@@ -4147,6 +4147,7 @@ static void rdtgroup_init_mba(struct rdt_resource *r, struct resctrl_ctrl *ctrl,
 		cfg = &d->staged_config[CDP_NONE];
 		cfg->new_ctrl = resctrl_get_default_ctrlval(ctrl);
 		cfg->have_new_ctrl = true;
+		cfg->staged_ctrl = ctrl;
 	}
 }
 
diff --git a/include/linux/resctrl.h b/include/linux/resctrl.h
index 06482e766087..cfb4c17bcb53 100644
--- a/include/linux/resctrl.h
+++ b/include/linux/resctrl.h
@@ -121,10 +121,16 @@ struct pseudo_lock_region {
  * struct resctrl_staged_config - parsed configuration to be applied
  * @new_ctrl:		new ctrl value to be loaded
  * @have_new_ctrl:	whether the user provided new_ctrl is valid
+ * @staged_ctrl:	the control whose schemata line staged this config.
+ *			Used to tell a real duplicate domain in one schemata
+ *			line from the mirrored write of an emulated control
+ *			and the control that backs it, which share the same
+ *			staged config.
  */
 struct resctrl_staged_config {
 	u32			new_ctrl;
 	bool			have_new_ctrl;
+	struct resctrl_ctrl	*staged_ctrl;
 };
 
 enum resctrl_domain_type {
-- 
2.43.0



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

* [PATCH 07/23] resctrl: Rebuild resource_schemata subdirs on MBA mode change
  2026-07-16 21:02 [PATCH RFC 00/23] resctrl: MBA control emulation and ARM MPAM MB_NODE support Fenghua Yu
                   ` (5 preceding siblings ...)
  2026-07-16 21:02 ` [PATCH 06/23] resctrl: Mirror schemata for controls without MBW hardware Fenghua Yu
@ 2026-07-16 21:02 ` Fenghua Yu
  2026-07-16 21:02 ` [PATCH 08/23] Documentation: resctrl: document MBA control emulation Fenghua Yu
                   ` (15 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Fenghua Yu @ 2026-07-16 21:02 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
	Shaopeng Tan, Chen Yu, Babu Moger, Drew Fustini, Vikram Sethi,
	Shanker Donthineni, Newton Liu, Gavin Shan
  Cc: linux-kernel, linux-arm-kernel, Fenghua Yu

The resource_schemata directory layout is mode dependent: in legacy mode
an emulated control (one without MBW hardware) is nested under the default
control that emulates it, while in native mode every control sits directly
under resource_schemata. The mode file added earlier is read-only, so the
layout is built once at mount time and can never change.

Make the mode file writable and rebuild the control subdirectories when
the mode changes, so the tree stays consistent with the new mode and with
the schemata file contents. Add resctrl_ctrl_mb_mode_write() and register
it on the mode file (switching it from 0444 to 0644). Factor the
per-control directory creation out of resctrl_mkdir_schemata_dir() into
resctrl_ctrl_create_subdirs(), add resctrl_ctrl_remove_subdirs() to tear
the control directories down, and drive both from
resctrl_ctrl_rebuild_subdirs(), which is called from
resctrl_ctrl_mb_mode_write() whenever the mode actually changes.

Signed-off-by: Fenghua Yu <fenghuay@nvidia.com>
---
 fs/resctrl/rdtgroup.c | 334 ++++++++++++++++++++++++++++++++----------
 1 file changed, 260 insertions(+), 74 deletions(-)

diff --git a/fs/resctrl/rdtgroup.c b/fs/resctrl/rdtgroup.c
index a34c7ed8f874..d81877a5cd72 100644
--- a/fs/resctrl/rdtgroup.c
+++ b/fs/resctrl/rdtgroup.c
@@ -995,6 +995,15 @@ void *rdt_kn_parent_priv(struct kernfs_node *kn)
 	return rcu_dereference(kn->__parent)->priv;
 }
 
+static struct kernfs_node *rdt_kn_parent(struct kernfs_node *kn)
+{
+	/*
+	 * Valid within the RCU section it was obtained or while rdtgroup_mutex
+	 * is held.
+	 */
+	return rcu_dereference_check(kn->__parent, lockdep_is_held(&rdtgroup_mutex));
+}
+
 static int rdt_num_closids_show(struct kernfs_open_file *of,
 				struct seq_file *seq, void *v)
 {
@@ -2713,6 +2722,173 @@ static unsigned long fflags_from_resource(struct rdt_resource *r)
 	return WARN_ON_ONCE(1);
 }
 
+/*
+ * Format the sysfs directory name of @ctrl (for example "MB" or "MB_NODE")
+ * into @buf. Returns -ENOSPC if the name would be truncated.
+ */
+static int resctrl_ctrl_full_name(struct rdt_resource_final *f,
+				  struct resctrl_ctrl *ctrl, char *buf, size_t size)
+{
+	int ret;
+
+	ret = snprintf(buf, size, "%s%s%s",
+		       f->name, resctrl_ctrl_is_default(ctrl) ? "" : "_",
+		       resctrl_ctrl_is_default(ctrl) ? "" : resctrl_ctrl_name_str(ctrl->name));
+	if (ret >= size)
+		return -ENOSPC;
+
+	return 0;
+}
+
+static int resctrl_ctrl_create_subdir(struct kernfs_node *kn_dir,
+				      struct rdt_resource_final *f,
+				      struct resctrl_ctrl *ctrl, struct kernfs_node **kn_ctrl)
+{
+	char ctrl_full_name[20];
+	int ret;
+
+	ret = resctrl_ctrl_full_name(f, ctrl, ctrl_full_name, sizeof(ctrl_full_name));
+	if (ret)
+		return ret;
+
+	*kn_ctrl = kernfs_create_dir(kn_dir, ctrl_full_name, kn_dir->mode, ctrl);
+	if (IS_ERR(*kn_ctrl))
+		return PTR_ERR(*kn_ctrl);
+
+	return rdtgroup_kn_set_ugid(*kn_ctrl);
+}
+
+/*
+ * Create a control subdirectory for each control under the
+ * resource_schemata directory @kn_subdir.
+ *
+ * No need to cleanup on exit - caller removes the created directories on
+ * failure (either via the recursive kernfs_remove() of an ancestor or by
+ * rebuilding the subdirectories).
+ */
+static int resctrl_ctrl_create_subdirs(struct kernfs_node *kn_subdir,
+				       struct rdt_resource_final *f)
+{
+	struct kernfs_node *kn_ctrl, *kn_ctrl_def = NULL;
+	struct resctrl_ctrl *ctrl, *ctrl_def = NULL;
+	struct rdt_resource *r = f->res;
+	int ret;
+
+	lockdep_assert_held(&rdtgroup_mutex);
+
+	/*
+	 * Create the default control sub-dir first. Emulated controls are
+	 * nested underneath it and may be iterated before it, so it has to
+	 * exist before the rest of the controls are created.
+	 */
+	for_each_resource_ctrl(ctrl, f->res) {
+		if (ctrl->name != RESCTRL_CTRL_NAME_DEF)
+			continue;
+
+		ret = resctrl_ctrl_create_subdir(kn_subdir, f, ctrl, &kn_ctrl_def);
+		if (ret)
+			return ret;
+		ret = resctrl_add_ctrl_files(kn_ctrl_def, ctrl);
+		if (ret)
+			return ret;
+		ctrl_def = ctrl;
+		break;
+	}
+
+	for_each_resource_ctrl(ctrl, f->res) {
+		if (ctrl->name == RESCTRL_CTRL_NAME_DEF)
+			continue;
+
+		/*
+		 * In legacy mode the emulating control is nested under the
+		 * default control it emulates (the default control's
+		 * emulated_by points at it). In native mode there is no
+		 * emulation, so every control sits directly under
+		 * resource_schemata.
+		 */
+		if (r->mode == RESCTRL_CTRL_LEGACY &&
+		    ctrl_def && ctrl_def->emulated_by == ctrl)
+			ret = resctrl_ctrl_create_subdir(kn_ctrl_def, f, ctrl, &kn_ctrl);
+		else
+			ret = resctrl_ctrl_create_subdir(kn_subdir, f, ctrl, &kn_ctrl);
+		if (ret)
+			return ret;
+
+		ret = resctrl_add_ctrl_files(kn_ctrl, ctrl);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * Remove every control subdirectory under the resource_schemata directory
+ * @kn_subdir, leaving the directory itself and its own files (for example
+ * "mode") in place. The default control directory is removed recursively so
+ * any control nested underneath it is removed too; removing a control that is
+ * not a direct child is a no-op.
+ */
+static void resctrl_ctrl_remove_subdirs(struct kernfs_node *kn_subdir,
+					struct rdt_resource_final *f)
+{
+	char ctrl_full_name[20];
+	struct resctrl_ctrl *ctrl;
+
+	lockdep_assert_held(&rdtgroup_mutex);
+
+	for_each_resource_ctrl(ctrl, f->res) {
+		if (resctrl_ctrl_full_name(f, ctrl, ctrl_full_name, sizeof(ctrl_full_name)))
+			continue;
+		kernfs_remove_by_name(kn_subdir, ctrl_full_name);
+	}
+}
+
+/*
+ * Rebuild the control subdirectories under resource_schemata to match the
+ * current mode. Used when the mode changes at runtime.
+ *
+ * The control directories have the same names in both modes (only their
+ * nesting differs), so the old directories must be removed before the new
+ * ones can be created - kernfs_create_dir() would otherwise fail with
+ * -EEXIST. Between the removal and the following kernfs_activate() there is
+ * a brief window in which resource_schemata contains only its own files (for
+ * example "mode") and no control subdirectories. This is acceptable: the
+ * rebuild only happens on an explicit mode write, runs under rdtgroup_mutex,
+ * and the control subdirectories are purely informational (scope, type,
+ * status) - the actual bandwidth configuration lives in each group's
+ * "schemata" file and is unaffected.
+ *
+ * On failure any partially created subdirectories are removed, so
+ * resource_schemata is left with no control subdirectories at all (its own
+ * files such as "mode" remain). The caller is expected to restore a
+ * consistent layout, typically by reverting the change that triggered the
+ * rebuild and calling this function again. If that second rebuild also
+ * fails there is no further automatic recovery and the control
+ * subdirectories stay missing until the next successful rebuild or a
+ * remount; the caller should report this to user space.
+ */
+static int resctrl_ctrl_rebuild_subdirs(struct kernfs_node *kn_subdir,
+					struct rdt_resource_final *f)
+{
+	int ret;
+
+	lockdep_assert_held(&rdtgroup_mutex);
+
+	resctrl_ctrl_remove_subdirs(kn_subdir, f);
+
+	ret = resctrl_ctrl_create_subdirs(kn_subdir, f);
+	if (ret) {
+		/* Drop any partially created subdirectories. */
+		resctrl_ctrl_remove_subdirs(kn_subdir, f);
+		return ret;
+	}
+
+	kernfs_activate(kn_subdir);
+
+	return 0;
+}
+
 static int resctrl_ctrl_mb_mode_show(struct kernfs_open_file *of,
 				     struct seq_file *seq, void *v)
 {
@@ -2738,12 +2914,91 @@ static int resctrl_ctrl_mb_mode_show(struct kernfs_open_file *of,
 	return 0;
 }
 
+static ssize_t resctrl_ctrl_mb_mode_write(struct kernfs_open_file *of,
+					  char *buf, size_t nbytes, loff_t off)
+{
+	struct rdt_resource_final *f = rdt_kn_parent_priv(of->kn);
+	struct rdt_resource *r = f->res;
+	enum resctrl_ctrl_mode mode;
+	int ret = 0;
+
+	guard(mutex)(&rdtgroup_mutex);
+	rdt_last_cmd_clear();
+
+	/* Valid input requires a trailing newline */
+	if (nbytes == 0 || buf[nbytes - 1] != '\n') {
+		rdt_last_cmd_puts("Invalid input\n");
+		return -EINVAL;
+	}
+
+	buf[nbytes - 1] = '\0';
+
+	if (!strcmp(buf, "native")) {
+		mode = RESCTRL_CTRL_NATIVE;
+	} else if (!strcmp(buf, "legacy")) {
+		mode = RESCTRL_CTRL_LEGACY;
+	} else {
+		rdt_last_cmd_puts("Invalid input\n");
+		return -EINVAL;
+	}
+
+	if (mode != r->mode) {
+		enum resctrl_ctrl_mode old_mode = r->mode;
+
+		r->mode = mode;
+
+		/*
+		 * The resource_schemata directory layout depends on the mode
+		 * (emulated controls are nested in legacy mode only), so rebuild
+		 * the control subdirectories to match the new mode.
+		 */
+		ret = resctrl_ctrl_rebuild_subdirs(rdt_kn_parent(of->kn), f);
+		if (ret) {
+			int rollback_ret;
+
+			/*
+			 * Roll back to the previous mode and rebuild so the
+			 * mode reported by this file stays consistent with the
+			 * directory layout.
+			 */
+			r->mode = old_mode;
+			rollback_ret = resctrl_ctrl_rebuild_subdirs(rdt_kn_parent(of->kn), f);
+			if (rollback_ret) {
+				/*
+				 * The rollback rebuild also failed:
+				 * resource_schemata may now be missing control
+				 * subdirectories. Report the inconsistency to the
+				 * kernel log and to last_cmd_status, and return
+				 * -EIO so user space can tell from the write()
+				 * error that the filesystem state is unreliable
+				 * rather than just that the mode switch failed.
+				 */
+				pr_err("%s: failed to restore resource_schemata (mode change error %d, restore error %d), control subdirectories may be missing\n",
+				       f->name, ret, rollback_ret);
+				rdt_last_cmd_puts("resource_schemata rebuild failed\n");
+				ret = -EIO;
+			} else {
+				/*
+				 * The requested mode change failed but the
+				 * previous mode and its directory layout were
+				 * restored, so the filesystem is consistent. Keep
+				 * the original error from the forward rebuild.
+				 */
+				rdt_last_cmd_puts("Failed to switch mode, reverted\n");
+			}
+		}
+	}
+
+	return ret ?: nbytes;
+}
+
 static struct rftype resctrl_ctrl_mb_files[] = {
 	{
 		.name		= "mode",
-		.mode		= 0444,
+		.mode		= 0644,
 		.kf_ops		= &rdtgroup_kf_single_ops,
 		.seq_show	= resctrl_ctrl_mb_mode_show,
+		.write		= resctrl_ctrl_mb_mode_write,
 		/*
 		 * Directory-level file, not per-control: fflags is only a
 		 * presence flag here, not the BIT(ctrl->type) type filter used
@@ -2781,26 +3036,6 @@ static int resctrl_ctrl_add_files(struct kernfs_node *kn)
 	return ret;
 }
 
-static int resctrl_ctrl_create_subdir(struct kernfs_node *kn_dir,
-				      struct rdt_resource_final *f,
-				      struct resctrl_ctrl *ctrl, struct kernfs_node **kn_ctrl)
-{
-	char ctrl_full_name[20];
-	int ret;
-
-	ret = snprintf(ctrl_full_name, sizeof(ctrl_full_name), "%s%s%s",
-		       f->name, resctrl_ctrl_is_default(ctrl) ? "" : "_",
-		       resctrl_ctrl_is_default(ctrl) ? "" : resctrl_ctrl_name_str(ctrl->name));
-	if (ret >= sizeof(ctrl_full_name))
-		return -ENOSPC;
-
-	*kn_ctrl = kernfs_create_dir(kn_dir, ctrl_full_name, kn_dir->mode, ctrl);
-	if (IS_ERR(*kn_ctrl))
-		return PTR_ERR(*kn_ctrl);
-
-	return rdtgroup_kn_set_ugid(*kn_ctrl);
-}
-
 /*
  * No need to cleanup on exit - caller calls the recursive kernfs_remove()
  * on failure.
@@ -2808,8 +3043,7 @@ static int resctrl_ctrl_create_subdir(struct kernfs_node *kn_dir,
 static int resctrl_mkdir_schemata_dir(struct kernfs_node *kn,
 				      struct rdt_resource_final *f)
 {
-	struct kernfs_node *kn_subdir, *kn_ctrl, *kn_ctrl_def = NULL;
-	struct resctrl_ctrl *ctrl, *ctrl_def = NULL;
+	struct kernfs_node *kn_subdir;
 	struct rdt_resource *r = f->res;
 	int ret;
 
@@ -2827,48 +3061,9 @@ static int resctrl_mkdir_schemata_dir(struct kernfs_node *kn,
 			return ret;
 	}
 
-	/*
-	 * Create the default control sub-dir first. Emulated controls are
-	 * nested underneath it and may be iterated before it, so it has to
-	 * exist before the rest of the controls are created.
-	 */
-	for_each_resource_ctrl(ctrl, f->res) {
-		if (ctrl->name != RESCTRL_CTRL_NAME_DEF)
-			continue;
-
-		ret = resctrl_ctrl_create_subdir(kn_subdir, f, ctrl, &kn_ctrl_def);
-		if (ret)
-			return ret;
-		ret = resctrl_add_ctrl_files(kn_ctrl_def, ctrl);
-		if (ret)
-			return ret;
-		ctrl_def = ctrl;
-		break;
-	}
-
-	for_each_resource_ctrl(ctrl, f->res) {
-		if (ctrl->name == RESCTRL_CTRL_NAME_DEF)
-			continue;
-
-		/*
-		 * In legacy mode the emulating control is nested under the
-		 * default control it emulates (the default control's
-		 * emulated_by points at it). In native mode there is no
-		 * emulation, so every control sits directly under
-		 * resource_schemata.
-		 */
-		if (r->mode == RESCTRL_CTRL_LEGACY &&
-		    ctrl_def && ctrl_def->emulated_by == ctrl)
-			ret = resctrl_ctrl_create_subdir(kn_ctrl_def, f, ctrl, &kn_ctrl);
-		else
-			ret = resctrl_ctrl_create_subdir(kn_subdir, f, ctrl, &kn_ctrl);
-		if (ret)
-			return ret;
-
-		ret = resctrl_add_ctrl_files(kn_ctrl, ctrl);
-		if (ret)
-			return ret;
-	}
+	ret = resctrl_ctrl_create_subdirs(kn_subdir, f);
+	if (ret)
+		return ret;
 
 	kernfs_activate(kn_subdir);
 
@@ -4566,15 +4761,6 @@ static int rdtgroup_rmdir_ctrl(struct rdtgroup *rdtgrp, cpumask_var_t tmpmask)
 	return 0;
 }
 
-static struct kernfs_node *rdt_kn_parent(struct kernfs_node *kn)
-{
-	/*
-	 * Valid within the RCU section it was obtained or while rdtgroup_mutex
-	 * is held.
-	 */
-	return rcu_dereference_check(kn->__parent, lockdep_is_held(&rdtgroup_mutex));
-}
-
 static int rdtgroup_rmdir(struct kernfs_node *kn)
 {
 	struct kernfs_node *parent_kn;
-- 
2.43.0



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

* [PATCH 08/23] Documentation: resctrl: document MBA control emulation
  2026-07-16 21:02 [PATCH RFC 00/23] resctrl: MBA control emulation and ARM MPAM MB_NODE support Fenghua Yu
                   ` (6 preceding siblings ...)
  2026-07-16 21:02 ` [PATCH 07/23] resctrl: Rebuild resource_schemata subdirs on MBA mode change Fenghua Yu
@ 2026-07-16 21:02 ` Fenghua Yu
  2026-07-16 21:02 ` [PATCH 09/23] resctrl: De-hardcode L3 monitor infrastructure Fenghua Yu
                   ` (14 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Fenghua Yu @ 2026-07-16 21:02 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
	Shaopeng Tan, Chen Yu, Babu Moger, Drew Fustini, Vikram Sethi,
	Shanker Donthineni, Newton Liu, Gavin Shan
  Cc: linux-kernel, linux-arm-kernel, Fenghua Yu

Describe the generic resctrl mechanism that lets a native control emulate
the legacy MB control so the MB: schemata line keeps working for existing
tools when the default MB control has no hardware of its own.

Document the info/<resource>/resource_schemata directory, its per-control
status file, and the writable legacy/native mode file, independent of any
particular architecture. Architecture-specific examples are added
separately.

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

diff --git a/Documentation/filesystems/resctrl.rst b/Documentation/filesystems/resctrl.rst
index e4b66af55ffb..0de86d00c0a6 100644
--- a/Documentation/filesystems/resctrl.rst
+++ b/Documentation/filesystems/resctrl.rst
@@ -81,6 +81,28 @@ to modify those settings.
 Each subdirectory contains the following files with respect to
 allocation:
 
+"resource_schemata":
+		Directory describing the controls exposed for each alloc-capable
+		resource (for example L3, L2, and MB). Each control has its own
+		subdirectory. Control names are formed from the resource name and
+		an optional suffix when a resource exposes more than one control
+		(for example ``MB`` and ``MB_NODE`` on MBA resources).
+
+		Each control subdirectory is read-only and contains ``scope``
+		and ``type`` files. Bandwidth (scalar) controls also expose a
+		``status`` file that reads ``enabled`` when the control is backed
+		by bandwidth-control hardware and ``disabled`` otherwise.
+
+		On resources that support control emulation (see "MBA control
+		emulation" below), this directory also contains a writable
+		``mode`` file that selects the active control mode (``legacy``
+		or ``native``). This is not the same as the per-resource-group
+		``mode`` file described below under "Resource Groups", which
+		selects shareable/exclusive cache allocation. Switching emulation
+		modes only changes observable behaviour when the default control
+		is disabled and emulated by another control. On resources that do
+		not support control emulation, no ``mode`` file is created.
+
 Cache resource(L3/L2)  subdirectory contains the following files
 related to allocation:
 
@@ -617,6 +639,10 @@ When control is enabled all CTRL_MON groups will also contain:
 	file. On successful pseudo-locked region creation the mode will
 	automatically change to "pseudo-locked".
 
+	This is not the ``mode`` file under ``info/<resource>/resource_schemata/``,
+	which selects MBA control emulation mode (``legacy`` or ``native``); see
+	"MBA control emulation" below.
+
 "ctrl_hw_id":
 	Available only with debug option. The identifier used by hardware
 	for the control group. On x86 this is the CLOSID.
@@ -966,6 +992,73 @@ Memory b/w domain is L3 cache.
 
 	MB:<cache_id0>=bandwidth0;<cache_id1>=bandwidth1;...
 
+MBA control emulation
+---------------------
+Some platforms expose memory bandwidth allocation through a native control
+(for example a node-scoped control) rather than the legacy ``MB`` control
+that existing tools expect. To preserve backward compatibility, legacy mode
+(the default) keeps the ``MB:`` entry in ``schemata`` when the default
+``MB`` control has no hardware of its own. A native control emulates ``MB``
+behind the scenes so user tools that read or write only the ``MB:`` line
+continue to work. In native mode the disabled ``MB`` control has no
+``schemata`` line; tools use the native control's line instead.
+
+Nesting under ``resource_schemata`` and schemata mirroring take effect
+only when an architecture driver configures which control emulates which
+(for example by setting the ``emulated_by`` relationship described in the
+kernel API).
+
+When the default ``MB`` control has no MBW hardware of its own
+(``status`` reads ``disabled``), it is emulated by a native control in
+legacy mode. In that case:
+
+- The native control is nested under ``MB`` in
+  ``info/MB/resource_schemata/``.
+- Schemata reads and writes for the ``MB:`` line are mirrored through
+  the native control so both lines show and update the same values.
+
+When ``MB`` is enabled, the native control is a sibling of ``MB`` in
+``info/MB/resource_schemata/``, and the two controls operate
+independently.
+
+Emulation mode
+~~~~~~~~~~~~~~
+Whether a disabled control is emulated is selected by the writable
+``mode`` file in ``info/MB/resource_schemata/``. This is not the same
+as the per-resource-group ``mode`` file under each ``<group>/`` directory,
+which selects shareable/exclusive cache allocation; see "Resource Groups"
+above. Reading it shows the available modes with the active one in brackets,
+for example::
+
+	# cat /sys/fs/resctrl/info/MB/resource_schemata/mode
+	[legacy] native
+
+``legacy`` (default):
+	A disabled ``MB`` control is emulated by a native control. The
+	native control is nested under ``MB`` in
+	``info/MB/resource_schemata/`` and schemata reads and writes for
+	the ``MB:`` line are mirrored through it. This keeps the ``MB:``
+	entry working for existing tools.
+
+``native``:
+	No emulation is performed. A disabled ``MB`` control has no ``MB:``
+	schemata line, the native control is a sibling of ``MB`` directly
+	under ``info/MB/resource_schemata/``, and each visible schemata line
+	only reflects its own hardware.
+
+The mode is changed by writing to the file::
+
+	# echo native > /sys/fs/resctrl/info/MB/resource_schemata/mode
+
+Switching mode rebuilds the ``info/MB/resource_schemata/`` control
+subdirectories so their nesting matches the new mode.
+
+While a mode write is being processed the control subdirectories are torn
+down and recreated, so a concurrent reader may briefly observe
+``resource_schemata/`` containing only the ``mode`` file. This is expected:
+the control subdirectories are informational only and the actual bandwidth
+configuration in each group's ``schemata`` file is unaffected.
+
 Memory bandwidth Allocation specified in MiBps
 ----------------------------------------------
 
-- 
2.43.0



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

* [PATCH 09/23] resctrl: De-hardcode L3 monitor infrastructure
  2026-07-16 21:02 [PATCH RFC 00/23] resctrl: MBA control emulation and ARM MPAM MB_NODE support Fenghua Yu
                   ` (7 preceding siblings ...)
  2026-07-16 21:02 ` [PATCH 08/23] Documentation: resctrl: document MBA control emulation Fenghua Yu
@ 2026-07-16 21:02 ` Fenghua Yu
  2026-07-16 21:03 ` [PATCH 10/23] resctrl: Expose MBA MBM counter assignment sysfs Fenghua Yu
                   ` (13 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Fenghua Yu @ 2026-07-16 21:02 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
	Shaopeng Tan, Chen Yu, Babu Moger, Drew Fustini, Vikram Sethi,
	Shanker Donthineni, Newton Liu, Gavin Shan
  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 4da54d2d816b..1cca9b147fdb 100644
--- a/fs/resctrl/internal.h
+++ b/fs/resctrl/internal.h
@@ -380,9 +380,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 8dfc54f8a1f2..742429e18b8a 100644
--- a/fs/resctrl/monitor.c
+++ b/fs/resctrl/monitor.c
@@ -427,7 +427,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;
 	}
@@ -573,7 +573,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);
@@ -1638,9 +1638,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;
@@ -1685,6 +1685,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.
@@ -1779,10 +1785,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;
@@ -1827,6 +1833,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)) {
@@ -1866,40 +1879,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))
@@ -1917,21 +1911,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 d81877a5cd72..d9876bbc6365 100644
--- a/fs/resctrl/rdtgroup.c
+++ b/fs/resctrl/rdtgroup.c
@@ -5032,10 +5032,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);
@@ -5068,7 +5075,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
@@ -5142,10 +5149,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);
@@ -5268,13 +5283,13 @@ int resctrl_init(void)
 
 	io_alloc_init();
 
-	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;
 	}
 
@@ -5309,7 +5324,7 @@ int resctrl_init(void)
 
 cleanup_mountpoint:
 	sysfs_remove_mount_point(fs_kobj, "resctrl");
-	resctrl_l3_mon_resource_exit();
+	resctrl_mon_exit();
 
 	return ret;
 }
@@ -5348,7 +5363,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.
@@ -5375,6 +5390,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.43.0



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

* [PATCH 10/23] resctrl: Expose MBA MBM counter assignment sysfs
  2026-07-16 21:02 [PATCH RFC 00/23] resctrl: MBA control emulation and ARM MPAM MB_NODE support Fenghua Yu
                   ` (8 preceding siblings ...)
  2026-07-16 21:02 ` [PATCH 09/23] resctrl: De-hardcode L3 monitor infrastructure Fenghua Yu
@ 2026-07-16 21:03 ` Fenghua Yu
  2026-07-16 21:03 ` [PATCH 11/23] resctrl: name node-scoped monitor domains mon_NODE_<id> Fenghua Yu
                   ` (12 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Fenghua Yu @ 2026-07-16 21:03 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
	Shaopeng Tan, Chen Yu, Babu Moger, Drew Fustini, Vikram Sethi,
	Shanker Donthineni, Newton Liu, Gavin Shan
  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_MB_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 1cca9b147fdb..3bf512bf2d9f 100644
--- a/fs/resctrl/internal.h
+++ b/fs/resctrl/internal.h
@@ -478,6 +478,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_MB_assignments_show(struct kernfs_open_file *of, struct seq_file *s, void *v);
+
+ssize_t mbm_MB_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 742429e18b8a..db19e5eac1f9 100644
--- a/fs/resctrl/monitor.c
+++ b/fs/resctrl/monitor.c
@@ -528,6 +528,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);
@@ -1691,6 +1692,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_MB_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.
@@ -1840,6 +1847,13 @@ ssize_t mbm_L3_assignments_write(struct kernfs_open_file *of, char *buf,
 			resctrl_arch_get_resource(RDT_RESOURCE_L3));
 }
 
+ssize_t mbm_MB_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)) {
@@ -1883,7 +1897,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;
@@ -1919,7 +1933,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_MB_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);
 	}
@@ -1952,6 +1969,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 d9876bbc6365..0bdcf6dd935c 100644
--- a/fs/resctrl/rdtgroup.c
+++ b/fs/resctrl/rdtgroup.c
@@ -2133,6 +2133,13 @@ static struct rftype res_common_files[] = {
 		.seq_show	= mbm_L3_assignments_show,
 		.write		= mbm_L3_assignments_write,
 	},
+	{
+		.name		= "mbm_MB_assignments",
+		.mode		= 0644,
+		.kf_ops		= &rdtgroup_kf_single_ops,
+		.seq_show	= mbm_MB_assignments_show,
+		.write		= mbm_MB_assignments_write,
+	},
 	{
 		.name		= "mbm_assign_mode",
 		.mode		= 0644,
@@ -5132,10 +5139,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.43.0



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

* [PATCH 11/23] resctrl: name node-scoped monitor domains mon_NODE_<id>
  2026-07-16 21:02 [PATCH RFC 00/23] resctrl: MBA control emulation and ARM MPAM MB_NODE support Fenghua Yu
                   ` (9 preceding siblings ...)
  2026-07-16 21:03 ` [PATCH 10/23] resctrl: Expose MBA MBM counter assignment sysfs Fenghua Yu
@ 2026-07-16 21:03 ` Fenghua Yu
  2026-07-16 21:03 ` [PATCH 12/23] resctrl: Add node-scope MBM total event Fenghua Yu
                   ` (11 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Fenghua Yu @ 2026-07-16 21:03 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
	Shaopeng Tan, Chen Yu, Babu Moger, Drew Fustini, Vikram Sethi,
	Shanker Donthineni, Newton Liu, Gavin Shan
  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 0bdcf6dd935c..db0255951f0e 100644
--- a/fs/resctrl/rdtgroup.c
+++ b/fs/resctrl/rdtgroup.c
@@ -3947,6 +3947,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
@@ -3990,7 +4004,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);
 
@@ -4094,7 +4108,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);
@@ -4149,8 +4163,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 cfb4c17bcb53..307803ea1b09 100644
--- a/include/linux/resctrl.h
+++ b/include/linux/resctrl.h
@@ -320,6 +320,7 @@ enum resctrl_scope {
 	RESCTRL_L3_CACHE = 3,
 	RESCTRL_L3_NODE,
 	RESCTRL_PACKAGE,
+	RESCTRL_NODE,
 };
 
 /**
-- 
2.43.0



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

* [PATCH 12/23] resctrl: Add node-scope MBM total event
  2026-07-16 21:02 [PATCH RFC 00/23] resctrl: MBA control emulation and ARM MPAM MB_NODE support Fenghua Yu
                   ` (10 preceding siblings ...)
  2026-07-16 21:03 ` [PATCH 11/23] resctrl: name node-scoped monitor domains mon_NODE_<id> Fenghua Yu
@ 2026-07-16 21:03 ` Fenghua Yu
  2026-07-16 21:03 ` [PATCH 13/23] resctrl: Make MBM paths resource-aware Fenghua Yu
                   ` (10 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Fenghua Yu @ 2026-07-16 21:03 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
	Shaopeng Tan, Chen Yu, Babu Moger, Drew Fustini, Vikram Sethi,
	Shanker Donthineni, Newton Liu, Gavin Shan
  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          |  1 +
 include/linux/resctrl.h       | 27 +++++++++++++++++++++++----
 include/linux/resctrl_types.h | 15 ++++++++++++++-
 3 files changed, 38 insertions(+), 5 deletions(-)

diff --git a/fs/resctrl/monitor.c b/fs/resctrl/monitor.c
index db19e5eac1f9..25b5038b3e16 100644
--- a/fs/resctrl/monitor.c
+++ b/fs/resctrl/monitor.c
@@ -990,6 +990,7 @@ 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 307803ea1b09..3fb236c459dc 100644
--- a/include/linux/resctrl.h
+++ b/include/linux/resctrl.h
@@ -536,16 +536,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.43.0



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

* [PATCH 13/23] resctrl: Make MBM paths resource-aware
  2026-07-16 21:02 [PATCH RFC 00/23] resctrl: MBA control emulation and ARM MPAM MB_NODE support Fenghua Yu
                   ` (11 preceding siblings ...)
  2026-07-16 21:03 ` [PATCH 12/23] resctrl: Add node-scope MBM total event Fenghua Yu
@ 2026-07-16 21:03 ` Fenghua Yu
  2026-07-16 21:03 ` [PATCH 14/23] arm_mpam: Support memory-level MSCs and ABMC per class Fenghua Yu
                   ` (9 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Fenghua Yu @ 2026-07-16 21:03 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
	Shaopeng Tan, Chen Yu, Babu Moger, Drew Fustini, Vikram Sethi,
	Shanker Donthineni, Newton Liu, Gavin Shan
  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    |  53 ++++++++++++--------
 3 files changed, 102 insertions(+), 61 deletions(-)

diff --git a/fs/resctrl/ctrlmondata.c b/fs/resctrl/ctrlmondata.c
index ee7823cf9493..77221c6f1e9a 100644
--- a/fs/resctrl/ctrlmondata.c
+++ b/fs/resctrl/ctrlmondata.c
@@ -693,7 +693,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;
@@ -724,6 +726,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 25b5038b3e16..665a28bdca7d 100644
--- a/fs/resctrl/monitor.c
+++ b/fs/resctrl/monitor.c
@@ -790,11 +790,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);
+	}
 }
 
 /*
@@ -863,8 +867,8 @@ void mbm_handle_overflow(struct work_struct *work)
 	if (!resctrl_mounted || !resctrl_arch_mon_capable())
 		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);
 
 	list_for_each_entry(prgrp, &rdt_all_groups, rdtgroup_list) {
 		mbm_update(r, d, prgrp);
@@ -873,7 +877,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);
 	}
 
@@ -1267,19 +1271,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);
+	}
 }
 
 /*
@@ -1326,18 +1331,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)
@@ -1497,6 +1503,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;
 
@@ -1548,13 +1555,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;
@@ -1896,6 +1909,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;
@@ -1905,6 +1919,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",
@@ -1913,17 +1932,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 db0255951f0e..5be285487df7 100644
--- a/fs/resctrl/rdtgroup.c
+++ b/fs/resctrl/rdtgroup.c
@@ -129,7 +129,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));
 }
 
 /*
@@ -1766,7 +1767,7 @@ static int mbm_total_bytes_config_show(struct kernfs_open_file *of,
 {
 	struct rdt_resource *r = rdt_kn_parent_priv(of->kn);
 
-	mbm_config_show(seq, r, QOS_L3_MBM_TOTAL_EVENT_ID);
+	mbm_config_show(seq, r, resctrl_mbm_total_event_id());
 
 	return 0;
 }
@@ -1885,7 +1886,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:
 	mutex_unlock(&rdtgroup_mutex);
@@ -5250,10 +5251,32 @@ static struct rdt_l3_mon_domain *get_mon_domain_from_cpu(int cpu,
 	return NULL;
 }
 
-void resctrl_offline_cpu(unsigned int cpu)
+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;
+
+	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) {
+		cancel_delayed_work(&d->mbm_over);
+		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)) {
+		cancel_delayed_work(&d->cqm_limbo);
+		cqm_setup_limbo_handler(d, 0, cpu);
+	}
+}
+
+void resctrl_offline_cpu(unsigned int cpu)
+{
 	struct rdtgroup *rdtgrp;
 
 	mutex_lock(&rdtgroup_mutex);
@@ -5264,23 +5287,11 @@ void resctrl_offline_cpu(unsigned int cpu)
 		}
 	}
 
-	if (!l3->mon_capable)
-		goto out_unlock;
-
-	d = get_mon_domain_from_cpu(cpu, l3);
-	if (d) {
-		if (resctrl_is_mbm_enabled() && cpu == d->mbm_work_cpu) {
-			cancel_delayed_work(&d->mbm_over);
-			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)) {
-			cancel_delayed_work(&d->cqm_limbo);
-			cqm_setup_limbo_handler(d, 0, cpu);
-		}
-	}
+	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));
 
-out_unlock:
 	mutex_unlock(&rdtgroup_mutex);
 }
 
-- 
2.43.0



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

* [PATCH 14/23] arm_mpam: Support memory-level MSCs and ABMC per class
  2026-07-16 21:02 [PATCH RFC 00/23] resctrl: MBA control emulation and ARM MPAM MB_NODE support Fenghua Yu
                   ` (12 preceding siblings ...)
  2026-07-16 21:03 ` [PATCH 13/23] resctrl: Make MBM paths resource-aware Fenghua Yu
@ 2026-07-16 21:03 ` Fenghua Yu
  2026-07-16 21:03 ` [PATCH 15/23] arm_mpam: Refine L3 topology and class selection Fenghua Yu
                   ` (8 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Fenghua Yu @ 2026-07-16 21:03 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
	Shaopeng Tan, Chen Yu, Babu Moger, Drew Fustini, Vikram Sethi,
	Shanker Donthineni, Newton Liu, Gavin Shan
  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 e2cb3384820a..953412f11995 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)
@@ -170,24 +181,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,
@@ -393,6 +421,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;
@@ -1149,7 +1178,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);
 		}
 	}
 }
@@ -1176,7 +1208,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;
 	}
@@ -1193,7 +1225,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)
@@ -1291,10 +1323,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;
 
@@ -1305,6 +1337,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))
@@ -1312,12 +1350,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;
 }
@@ -1325,8 +1363,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();
 
@@ -1353,8 +1398,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
@@ -1364,9 +1414,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);
@@ -1374,13 +1424,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.43.0



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

* [PATCH 15/23] arm_mpam: Refine L3 topology and class selection
  2026-07-16 21:02 [PATCH RFC 00/23] resctrl: MBA control emulation and ARM MPAM MB_NODE support Fenghua Yu
                   ` (13 preceding siblings ...)
  2026-07-16 21:03 ` [PATCH 14/23] arm_mpam: Support memory-level MSCs and ABMC per class Fenghua Yu
@ 2026-07-16 21:03 ` Fenghua Yu
  2026-07-16 21:03 ` [PATCH 16/23] arm_mpam: Include all MSC components during domain setup Fenghua Yu
                   ` (7 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Fenghua Yu @ 2026-07-16 21:03 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
	Shaopeng Tan, Chen Yu, Babu Moger, Drew Fustini, Vikram Sethi,
	Shanker Donthineni, Newton Liu, Gavin Shan
  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 953412f11995..86458a56a526 100644
--- a/drivers/resctrl/mpam_resctrl.c
+++ b/drivers/resctrl/mpam_resctrl.c
@@ -854,10 +854,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;
 
@@ -871,8 +873,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);
@@ -892,6 +897,7 @@ static bool topology_matches_l3(struct mpam_class *victim)
 
 			return false;
 		}
+		matched_once = true;
 	}
 
 	return true;
@@ -1030,13 +1036,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;
@@ -1164,8 +1172,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);
 
@@ -1302,6 +1311,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.43.0



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

* [PATCH 16/23] arm_mpam: Include all MSC components during domain setup
  2026-07-16 21:02 [PATCH RFC 00/23] resctrl: MBA control emulation and ARM MPAM MB_NODE support Fenghua Yu
                   ` (14 preceding siblings ...)
  2026-07-16 21:03 ` [PATCH 15/23] arm_mpam: Refine L3 topology and class selection Fenghua Yu
@ 2026-07-16 21:03 ` Fenghua Yu
  2026-07-16 21:03 ` [PATCH 17/23] arm_mpam: Handle CPU-less numa nodes Fenghua Yu
                   ` (6 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Fenghua Yu @ 2026-07-16 21:03 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
	Shaopeng Tan, Chen Yu, Babu Moger, Drew Fustini, Vikram Sethi,
	Shanker Donthineni, Newton Liu, Gavin Shan
  Cc: linux-kernel, linux-arm-kernel, Fenghua Yu

Iterate all MSC components per CPU when creating and looking up control
and monitor domains on the controls branch. Key monitor domains by
component, support MBA mon_capable lookup, and report mon capability
from any L3 or MBA monitor resource.

Signed-off-by: Fenghua Yu <fenghuay@nvidia.com>
---
 drivers/resctrl/mpam_resctrl.c | 197 +++++++++++++++++++--------------
 1 file changed, 112 insertions(+), 85 deletions(-)

diff --git a/drivers/resctrl/mpam_resctrl.c b/drivers/resctrl/mpam_resctrl.c
index 86458a56a526..f67647d95bbe 100644
--- a/drivers/resctrl/mpam_resctrl.c
+++ b/drivers/resctrl/mpam_resctrl.c
@@ -95,11 +95,27 @@ static bool mpam_class_memory(struct mpam_class *class)
 
 bool resctrl_arch_mon_capable(void)
 {
-	struct mpam_resctrl_res *res = &mpam_resctrl_controls[RDT_RESOURCE_L3];
-	struct rdt_resource *l3 = &res->resctrl_res;
+	enum resctrl_event_id eventid;
+	struct mpam_resctrl_mon *mon;
+	struct mpam_resctrl_res *res;
+	struct rdt_resource *r;
+
+	for_each_mpam_resctrl_mon(mon, eventid) {
+		if (!mon->class)
+			continue;	// dummy resource
+
+		if (mpam_class_memory(mon->class))
+			res = &mpam_resctrl_controls[RDT_RESOURCE_MBA];
+		else
+			res = &mpam_resctrl_controls[RDT_RESOURCE_L3];
+
+		r = &res->resctrl_res;
 
-	/* All monitors are presented as being on the L3 cache */
-	return l3->mon_capable;
+		if (r->mon_capable)
+			return true;
+	}
+
+	return false;
 }
 
 bool resctrl_arch_is_evt_configurable(enum resctrl_event_id evt)
@@ -1719,13 +1735,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);
@@ -1733,28 +1748,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)
@@ -1772,13 +1776,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;
@@ -1789,24 +1792,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
@@ -1821,7 +1814,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;
@@ -1858,25 +1851,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;
 	}
 
@@ -1884,14 +1884,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;
 	}
 
@@ -1902,6 +1907,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) {
@@ -1912,30 +1918,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);
 			}
 		}
 	}
@@ -1949,6 +1965,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);
 
@@ -1964,31 +1981,41 @@ void mpam_resctrl_offline_cpu(unsigned int cpu)
 		if (!res->class)
 			continue;	// dummy resource
 
-		if (r->alloc_capable) {
-			for_each_resource_ctrl(ctrl, r) {
-				dom = mpam_resctrl_get_ctrl_domain_from_cpu(cpu, ctrl);
+		guard(srcu)(&mpam_srcu);
+		list_for_each_entry_srcu(comp, &res->class->components, class_list,
+					 srcu_read_lock_held(&mpam_srcu)) {
+			if (!cpumask_test_cpu(cpu, &comp->affinity))
+				continue;
+
+			if (r->alloc_capable) {
+				for_each_resource_ctrl(ctrl, r) {
+					dom = mpam_resctrl_get_ctrl_domain_from_cpu(cpu, res,
+										    ctrl, comp);
+					if (WARN_ON_ONCE(!dom))
+						continue;
+					ctrl_d = &dom->resctrl_ctrl_dom;
+					dom_empty = mpam_resctrl_offline_domain_hdr(cpu,
+										    &ctrl_d->hdr);
+					if (dom_empty) {
+						resctrl_offline_ctrl_domain(&res->resctrl_res, ctrl,
+									    ctrl_d);
+						kfree(dom);
+					}
+				}
+			}
+
+			if (r->mon_capable) {
+				dom = mpam_resctrl_get_mon_domain_from_cpu(cpu, res, comp);
 				if (WARN_ON_ONCE(!dom))
 					continue;
-				ctrl_d = &dom->resctrl_ctrl_dom;
-				dom_empty = mpam_resctrl_offline_domain_hdr(cpu, &ctrl_d->hdr);
+				mon_d = &dom->resctrl_mon_dom;
+				dom_empty = mpam_resctrl_offline_domain_hdr(cpu, &mon_d->hdr);
 				if (dom_empty) {
-					resctrl_offline_ctrl_domain(&res->resctrl_res, ctrl, ctrl_d);
+					resctrl_offline_mon_domain(&res->resctrl_res, &mon_d->hdr);
 					kfree(dom);
 				}
 			}
 		}
-
-		if (r->mon_capable) {
-			dom = mpam_resctrl_get_mon_domain_from_cpu(cpu, res);
-			if (WARN_ON_ONCE(!dom))
-				continue;
-			mon_d = &dom->resctrl_mon_dom;
-			dom_empty = mpam_resctrl_offline_domain_hdr(cpu, &mon_d->hdr);
-			if (dom_empty) {
-				resctrl_offline_mon_domain(&res->resctrl_res, &mon_d->hdr);
-				kfree(dom);
-			}
-		}
 	}
 }
 
-- 
2.43.0



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

* [PATCH 17/23] arm_mpam: Handle CPU-less numa nodes
  2026-07-16 21:02 [PATCH RFC 00/23] resctrl: MBA control emulation and ARM MPAM MB_NODE support Fenghua Yu
                   ` (15 preceding siblings ...)
  2026-07-16 21:03 ` [PATCH 16/23] arm_mpam: Include all MSC components during domain setup Fenghua Yu
@ 2026-07-16 21:03 ` Fenghua Yu
  2026-07-16 21:03 ` [PATCH 18/23] arm_mpam: Emulate MB control with node-scoped MB_NODE control Fenghua Yu
                   ` (5 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Fenghua Yu @ 2026-07-16 21:03 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
	Shaopeng Tan, Chen Yu, Babu Moger, Drew Fustini, Vikram Sethi,
	Shanker Donthineni, Newton Liu, Gavin Shan
  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.43.0



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

* [PATCH 18/23] arm_mpam: Emulate MB control with node-scoped MB_NODE control
  2026-07-16 21:02 [PATCH RFC 00/23] resctrl: MBA control emulation and ARM MPAM MB_NODE support Fenghua Yu
                   ` (16 preceding siblings ...)
  2026-07-16 21:03 ` [PATCH 17/23] arm_mpam: Handle CPU-less numa nodes Fenghua Yu
@ 2026-07-16 21:03 ` Fenghua Yu
  2026-07-16 21:03 ` [PATCH 19/23] Documentation: arm64: mpam: document memory-level MB control and NUMA nodes Fenghua Yu
                   ` (4 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Fenghua Yu @ 2026-07-16 21:03 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
	Shaopeng Tan, Chen Yu, Babu Moger, Drew Fustini, Vikram Sethi,
	Shanker Donthineni, Newton Liu, Gavin Shan
  Cc: linux-kernel, linux-arm-kernel, Fenghua Yu

On memory-class MSCs the MB bandwidth control is node-scoped. Add a
node-scoped MB_NODE control that emulates the default MB control by
pointing its resctrl_ctrl::emulated at the default control, so it is
exposed nested under MB in resource_schemata.

Introduce mpam_resctrl_ctrl_node() to identify memory-class resources
and use it when selecting the control scope and the hard-limit control
name, replacing the open-coded mpam_class_memory() lookups. Expose the
NODE control scope in the per-control "scope" sysfs file so node-scoped
controls report "NODE" instead of "Unsupported control scope".

Signed-off-by: Fenghua Yu <fenghuay@nvidia.com>
---
 drivers/resctrl/mpam_resctrl.c | 166 ++++++++++++++++++++++++++++-----
 fs/resctrl/rdtgroup.c          |   3 +
 2 files changed, 146 insertions(+), 23 deletions(-)

diff --git a/drivers/resctrl/mpam_resctrl.c b/drivers/resctrl/mpam_resctrl.c
index f67647d95bbe..890b17c218a2 100644
--- a/drivers/resctrl/mpam_resctrl.c
+++ b/drivers/resctrl/mpam_resctrl.c
@@ -1253,20 +1253,133 @@ void resctrl_arch_config_cntr(struct rdt_resource *r, struct rdt_l3_mon_domain *
 	resctrl_arch_reset_cntr(r, d, closid, rmid, cntr_id, evtid);
 }
 
+/* Fixme: Is this a right helper? How to get MB and MB_NODE together */
+static bool mpam_resctrl_ctrl_node(struct rdt_resource *r)
+{
+	struct mpam_resctrl_res *res;
+	struct mpam_class *class;
+
+	res = container_of(r, struct mpam_resctrl_res, resctrl_res);
+	class = res->class;
+
+	return mpam_class_memory(class);
+}
+
+static bool mpam_resctrl_cache_has_mba(void)
+{
+	struct mpam_resctrl_res *l3_res = &mpam_resctrl_controls[RDT_RESOURCE_L3];
+	struct mpam_class *class = l3_res->class;
+
+	if (!class)
+		return false;
+
+	return class_has_usable_mba(&class->props);
+}
+
+static void _mpam_resctrl_ctrl_init_mba(struct rdt_resource *r,
+					struct mpam_resctrl_ctrl *mpam_ctrl,
+					struct mpam_props *cprops,
+					enum resctrl_ctrl_name name)
+{
+	mpam_ctrl->r_ctrl.type = RESCTRL_CTRL_SCALAR;
+	mpam_ctrl->r_ctrl.name = name;
+	INIT_LIST_HEAD_RCU(&mpam_ctrl->r_ctrl.domains);
+
+	mpam_ctrl->r_ctrl.membw.min_bw = get_mba_min(cprops);
+	mpam_ctrl->r_ctrl.membw.max_bw = MAX_MBA_BW;
+	mpam_ctrl->r_ctrl.membw.bw_gran = get_mba_granularity(cprops);
+
+	if (mpam_resctrl_ctrl_node(r)) {
+		/*
+		 * Default to legacy mode so a disabled MB control is emulated
+		 * by the node-scoped control, keeping the "MB:" schemata line
+		 * for backward compatibility. User space can switch to native
+		 * mode to disable emulation.
+		 */
+		r->mode = RESCTRL_CTRL_LEGACY;
+		/*
+		 * On a memory-class resource the legacy MB control is backed by
+		 * the L3 cache MBW hardware when the cache has usable MBA, so it
+		 * keeps cache scope. Otherwise it is node-scoped and emulated by
+		 * the MB_NODE control.
+		 */
+		if (name == RESCTRL_CTRL_NAME_DEF && mpam_resctrl_cache_has_mba())
+			mpam_ctrl->r_ctrl.scope = RESCTRL_L3_CACHE;
+		else
+			mpam_ctrl->r_ctrl.scope = RESCTRL_NODE;
+	} else {
+		mpam_ctrl->r_ctrl.scope = RESCTRL_L3_CACHE;
+		r->mode = RESCTRL_CTRL_LEGACY;
+	}
+}
+
+static void mpam_resctrl_ctrl_set_mbw_status(struct resctrl_ctrl *ctrl,
+					     struct rdt_resource *r,
+					     struct mpam_props *cprops)
+{
+	switch (ctrl->name) {
+	case RESCTRL_CTRL_NAME_DEF:
+		if (mpam_resctrl_ctrl_node(r))
+			ctrl->membw.no_mbw_hw = !mpam_resctrl_cache_has_mba();
+		else
+			ctrl->membw.no_mbw_hw = !class_has_usable_mba(cprops);
+		break;
+	case RESCTRL_CTRL_NAME_NODE:
+		ctrl->membw.no_mbw_hw = !class_has_usable_mba(cprops);
+		break;
+	default:
+		ctrl->membw.no_mbw_hw = true;
+		break;
+	}
+}
+
+static int mpam_resctrl_ctrl_init_mba(struct rdt_resource *r,
+				      struct mpam_props *cprops)
+{
+	struct mpam_resctrl_ctrl *ctrl_def;
+
+	ctrl_def = kzalloc_obj(*ctrl_def);
+	if (!ctrl_def)
+		return -ENOMEM;
+
+	_mpam_resctrl_ctrl_init_mba(r, ctrl_def, cprops, RESCTRL_CTRL_NAME_DEF);
+	mpam_resctrl_ctrl_set_mbw_status(&ctrl_def->r_ctrl, r, cprops);
+	list_add(&ctrl_def->r_ctrl.entry, &r->controls);
+
+	if (mpam_resctrl_ctrl_node(r)) {
+		struct mpam_resctrl_ctrl *ctrl_node;
+
+		ctrl_node = kzalloc_obj(*ctrl_node);
+		if (ctrl_node) {
+			_mpam_resctrl_ctrl_init_mba(r, ctrl_node, cprops,
+						    RESCTRL_CTRL_NAME_NODE);
+			mpam_resctrl_ctrl_set_mbw_status(&ctrl_node->r_ctrl, r,
+							 cprops);
+			if (ctrl_def->r_ctrl.membw.no_mbw_hw)
+				ctrl_def->r_ctrl.emulated_by = &ctrl_node->r_ctrl;
+
+			list_add(&ctrl_node->r_ctrl.entry, &r->controls);
+		}
+	}
+
+	return 0;
+}
+
 static int mpam_resctrl_control_init(struct mpam_resctrl_res *res)
 {
 	struct mpam_class *class = res->class;
 	struct mpam_props *cprops = &class->props;
 	struct rdt_resource *r = &res->resctrl_res;
 	struct mpam_resctrl_ctrl *mpam_ctrl;
-
-	mpam_ctrl = kzalloc_obj(*mpam_ctrl);
-	if (!mpam_ctrl)
-		return -ENOMEM;
+	int ret = 0;
 
 	switch (r->rid) {
 	case RDT_RESOURCE_L2:
 	case RDT_RESOURCE_L3:
+		mpam_ctrl = kzalloc_obj(*mpam_ctrl);
+		if (!mpam_ctrl)
+			return -ENOMEM;
+
 		mpam_ctrl->r_ctrl.type = RESCTRL_CTRL_BITMAP;
 		mpam_ctrl->r_ctrl.name = RESCTRL_CTRL_NAME_DEF;
 		INIT_LIST_HEAD_RCU(&mpam_ctrl->r_ctrl.domains);
@@ -1298,18 +1411,12 @@ static int mpam_resctrl_control_init(struct mpam_resctrl_res *res)
 		r->alloc_capable = true;
 		break;
 	case RDT_RESOURCE_MBA:
-		mpam_ctrl->r_ctrl.type = RESCTRL_CTRL_SCALAR;
-		mpam_ctrl->r_ctrl.scope = RESCTRL_L3_CACHE;
-		mpam_ctrl->r_ctrl.name = RESCTRL_CTRL_NAME_DEF;
-		INIT_LIST_HEAD_RCU(&mpam_ctrl->r_ctrl.domains);
+		ret = mpam_resctrl_ctrl_init_mba(r, cprops);
+		if (ret)
+			return ret;
 
 		r->bw_delay_linear = true;
 		r->bw_throttle_mode = THREAD_THROTTLE_UNDEFINED;
-		mpam_ctrl->r_ctrl.membw.min_bw = get_mba_min(cprops);
-		mpam_ctrl->r_ctrl.membw.max_bw = MAX_MBA_BW;
-		mpam_ctrl->r_ctrl.membw.bw_gran = get_mba_granularity(cprops);
-		list_add(&mpam_ctrl->r_ctrl.entry, &r->controls);
-
 		r->name = "MB";
 		r->alloc_capable = true;
 		break;
@@ -1472,7 +1579,7 @@ u32 resctrl_arch_get_config(struct rdt_resource *r, struct resctrl_ctrl *ctrl,
 	struct mpam_props *cprops;
 	struct mpam_resctrl_res *res;
 	struct mpam_resctrl_dom *dom;
-	enum mpam_device_features configured_by;
+	enum mpam_device_features configured_by = MPAM_FEATURE_LAST;
 
 	lockdep_assert_cpus_held();
 
@@ -1500,16 +1607,22 @@ u32 resctrl_arch_get_config(struct rdt_resource *r, struct resctrl_ctrl *ctrl,
 		configured_by = mpam_feat_cpor_part;
 		break;
 	case RDT_RESOURCE_MBA:
-		if (mpam_has_feature(mpam_feat_mbw_max, cprops)) {
-			configured_by = mpam_feat_mbw_max;
+		switch (ctrl->name) {
+		case RESCTRL_CTRL_NAME_DEF:
+		case RESCTRL_CTRL_NAME_NODE:
+			if (mpam_has_feature(mpam_feat_mbw_max, cprops))
+				configured_by = mpam_feat_mbw_max;
+			break;
+		default:
 			break;
 		}
-		fallthrough;
+		break;
 	default:
 		return resctrl_get_default_ctrlval(ctrl);
 	}
 
-	if (!r->alloc_capable || partid >= resctrl_arch_get_num_closid(r) ||
+	if (configured_by == MPAM_FEATURE_LAST || !r->alloc_capable ||
+	    partid >= resctrl_arch_get_num_closid(r) ||
 	    !mpam_has_feature(configured_by, cfg))
 		return resctrl_get_default_ctrlval(ctrl);
 
@@ -1570,12 +1683,19 @@ int resctrl_arch_update_one(struct rdt_resource *r, struct resctrl_ctrl *ctrl,
 		mpam_set_feature(mpam_feat_cpor_part, &cfg);
 		break;
 	case RDT_RESOURCE_MBA:
-		if (mpam_has_feature(mpam_feat_mbw_max, cprops)) {
-			cfg.mbw_max = percent_to_mbw_max(cfg_val, cprops);
-			mpam_set_feature(mpam_feat_mbw_max, &cfg);
-			break;
+		switch (ctrl->name) {
+		case RESCTRL_CTRL_NAME_DEF:
+		case RESCTRL_CTRL_NAME_NODE:
+			if (mpam_has_feature(mpam_feat_mbw_max, cprops)) {
+				cfg.mbw_max = percent_to_mbw_max(cfg_val, cprops);
+				mpam_set_feature(mpam_feat_mbw_max, &cfg);
+				break;
+			}
+			return -EINVAL;
+		default:
+			return -EINVAL;
 		}
-		fallthrough;
+		break;
 	default:
 		return -EINVAL;
 	}
diff --git a/fs/resctrl/rdtgroup.c b/fs/resctrl/rdtgroup.c
index 5be285487df7..b9e8d1183cdd 100644
--- a/fs/resctrl/rdtgroup.c
+++ b/fs/resctrl/rdtgroup.c
@@ -2239,6 +2239,9 @@ static int resctrl_ctrl_scope_show(struct kernfs_open_file *of,
 	case RESCTRL_L2_CACHE:
 		seq_puts(seq, "L2\n");
 		return 0;
+	case RESCTRL_NODE:
+		seq_puts(seq, "NODE\n");
+		return 0;
 	default:
 		/* resctrl does not yet support any other control scope */
 		seq_puts(seq, "Unsupported control scope\n");
-- 
2.43.0



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

* [PATCH 19/23] Documentation: arm64: mpam: document memory-level MB control and NUMA nodes
  2026-07-16 21:02 [PATCH RFC 00/23] resctrl: MBA control emulation and ARM MPAM MB_NODE support Fenghua Yu
                   ` (17 preceding siblings ...)
  2026-07-16 21:03 ` [PATCH 18/23] arm_mpam: Emulate MB control with node-scoped MB_NODE control Fenghua Yu
@ 2026-07-16 21:03 ` Fenghua Yu
  2026-07-16 21:03 ` [PATCH 20/23] Documentation: resctrl: document NODE-scoped MBA domains and mon_NODE monitoring Fenghua Yu
                   ` (3 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Fenghua Yu @ 2026-07-16 21:03 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
	Shaopeng Tan, Chen Yu, Babu Moger, Drew Fustini, Vikram Sethi,
	Shanker Donthineni, Newton Liu, Gavin Shan
  Cc: linux-kernel, linux-arm-kernel, Fenghua Yu

The MB control can be backed by a memory-level MSC above L3 rather than
the L3 cache MSC. In that case resctrl identifies domains by NUMA node id
instead of L3 cache id, exposes a node-scoped MB_NODE control, and can
manage CPU-less memory nodes by falling back to cpu_possible_mask.

Update the arm64 MPAM documentation to describe the L3-cache and memory
MSC paths for both the MB control and MBWU monitoring, how to tell them
apart via the control "scope" file, how NUMA node ids map to
/sys/devices/system/node/node*/, and CPU-less NUMA node handling.

Signed-off-by: Fenghua Yu <fenghuay@nvidia.com>
---
 Documentation/arch/arm64/mpam.rst | 105 +++++++++++++++++++-----------
 1 file changed, 68 insertions(+), 37 deletions(-)

diff --git a/Documentation/arch/arm64/mpam.rst b/Documentation/arch/arm64/mpam.rst
index 67fe515ed501..88ddee8705e4 100644
--- a/Documentation/arch/arm64/mpam.rst
+++ b/Documentation/arch/arm64/mpam.rst
@@ -44,48 +44,79 @@ The supported features are:
   placement.
 
 * Memory bandwidth maximum controls (MBW_MAX) on or after the L3 cache.
-  resctrl uses the L3 cache-id to identify where the memory bandwidth
-  control is applied. For this reason the platform must have an L3 cache
-  with cache-id's supplied by firmware. (It doesn't need to support MPAM.)
-
-  To be exported as the 'MB' schema, the topology of the group of MSC chosen
-  must match the topology of the L3 cache so that the cache-id's can be
-  repainted. For example: Platforms with Memory bandwidth maximum controls
-  on CPU-less NUMA nodes cannot expose the 'MB' schema to resctrl as these
-  nodes do not have a corresponding L3 cache. If the memory bandwidth
-  control is on the memory rather than the L3 then there must be a single
-  global L3 as otherwise it is unknown which L3 the traffic came from. There
-  must be no caches between the L3 and the memory so that the two ends of
-  the path have equivalent traffic.
-
-  When the MPAM driver finds multiple groups of MSC it can use for the 'MB'
-  schema, it prefers the group closest to the L3 cache.
+  resctrl exposes these as the ``MB`` resource.  The domain identifiers
+  used in the ``MB:`` schemata line depend on which MSC group backs the
+  resource:
+
+  **L3-cache MSC (cache-level MB control).**
+  When the MB control hardware sits on the L3 cache MSC, resctrl uses L3
+  cache-ids to identify where bandwidth is applied.  The topology of the
+  MSC group must match the L3 cache topology so that cache-ids can be
+  repainted.  If the memory bandwidth control is on the memory rather
+  than the L3 then there must be a single global L3 as otherwise it is
+  unknown which L3 the traffic came from.  There must be no caches
+  between the L3 and the memory so that the two ends of the path have
+  equivalent traffic.
+
+  **Memory MSC (memory-level MB control).**
+  When the MB control hardware sits on a memory MSC above L3, resctrl uses
+  NUMA node identifiers instead of L3 cache-ids.  The native node-scoped
+  control is exposed as ``MB_NODE``; the legacy ``MB`` control keeps the
+  ``MB:`` schemata line for backward compatibility and, in the default
+  legacy mode, is emulated by ``MB_NODE`` when it has no hardware of its
+  own.  See the MB control emulation section in
+  Documentation/filesystems/resctrl.rst for the control layout and
+  emulation modes.
+
+  On either path, read the control ``scope`` file under
+  ``info/MB/resource_schemata/`` (``L3`` or ``NODE``) to learn which
+  identifier space the ``MB:`` line uses.
+
+  When ``scope`` reads ``NODE``, each numeric ID ``XX`` in the ``MB:``
+  line is a NUMA node id and corresponds to the standard NUMA node
+  directory ``/sys/devices/system/node/nodeXX/``.  That directory
+  describes the node: ``cpulist``/``cpumap`` (the CPUs local to it, empty
+  for a CPU-less node), ``distance`` (NUMA distances to other nodes),
+  ``meminfo`` (its memory), and its ``memoryX`` symlinks.  Use these
+  files to map an ``MB:`` (or ``MB_NODE:``) entry to a physical NUMA node
+  and its CPUs and memory.
+
+  **CPU-less NUMA nodes.**
+  A memory MSC may be associated with a NUMA node that has no local CPUs
+  (for example a memory-only node that still participates in bandwidth
+  control).  The driver falls back to ``cpu_possible_mask`` for the MSC
+  affinity so that traffic from remote CPUs is still accounted for and
+  the node can appear as an ``MB``/``MB_NODE`` domain.
+
+  When the MPAM driver finds multiple groups of MSC it can use for the
+  ``MB`` resource, it prefers the group closest to the L3 cache.
 
 * Cache Storage Usage (CSU) counters can expose the 'llc_occupancy' provided
   there is at least one CSU monitor on each MSC that makes up the L3 group.
   Exposing CSU counters from other caches or devices is not supported.
 
-* Memory Bandwidth Usage (MBWU) on or after the L3 cache.  resctrl uses the
-  L3 cache-id to identify where the memory bandwidth is measured. For this
-  reason the platform must have an L3 cache with cache-id's supplied by
-  firmware. (The platform doesn't need to support MPAM.)
-
-  Memory bandwidth monitoring makes use of MBWU monitors in each MSC that
-  makes up the L3 group. If the memory bandwidth monitoring is on the memory
-  rather than the L3 then there must be a single global L3 as otherwise it
-  is unknown which L3 the traffic came from.
-
-  To expose 'mbm_total_bytes', the topology of the group of MSC chosen must
-  match the topology of the L3 cache so that the cache-id's can be
-  repainted. For example: Platforms with Memory bandwidth monitors on
-  CPU-less NUMA nodes cannot expose 'mbm_total_bytes' as these nodes do not
-  have a corresponding L3 cache. 'mbm_local_bytes' is not exposed as MPAM
-  cannot distinguish local traffic from global traffic.
-
-  All these restrictions based on L3 cache are due to resctrl, currently, only
-  supporting monitoring at the L3 scope. It is expected that going forward more
-  MBWU monitors can be exposed to the user after support for more monitoring
-  scopes is added to resctrl.
+* Memory Bandwidth Usage (MBWU) on or after the L3 cache.  resctrl can
+  expose ``mbm_total_bytes`` from either an L3-cache MSC or a memory MSC:
+
+  **L3-cache MSC.**
+  When MBWU monitors sit on the L3 cache MSC, counters are exposed on the
+  ``L3_MON`` resource and use L3 cache-ids.  The MSC group topology must
+  match the L3 cache topology so that cache-ids can be repainted.
+
+  **Memory MSC.**
+  When MBWU monitors sit on a memory MSC above L3, counters are exposed on
+  the ``MB`` resource with node scope.  Monitor directories are named
+  ``mon_NODE_XX`` where ``XX`` is the same NUMA node identifier that appears
+  in the ``MB:`` schemata line.  CPU-less memory nodes are supported the
+  same way as for MB controls above.
+
+  ``mbm_local_bytes`` is not exposed as MPAM cannot distinguish local
+  traffic from global traffic on these paths.
+
+  When only L3-scoped monitoring was supported, platforms with memory
+  bandwidth monitors on CPU-less NUMA nodes could not expose
+  ``mbm_total_bytes``.  Node-scoped monitoring on the ``MB`` resource
+  removes that restriction for memory-class MSCs.
 
 Reporting Bugs
 ==============
-- 
2.43.0



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

* [PATCH 20/23] Documentation: resctrl: document NODE-scoped MBA domains and mon_NODE monitoring
  2026-07-16 21:02 [PATCH RFC 00/23] resctrl: MBA control emulation and ARM MPAM MB_NODE support Fenghua Yu
                   ` (18 preceding siblings ...)
  2026-07-16 21:03 ` [PATCH 19/23] Documentation: arm64: mpam: document memory-level MB control and NUMA nodes Fenghua Yu
@ 2026-07-16 21:03 ` Fenghua Yu
  2026-07-16 21:03 ` [PATCH 21/23] Documentation: resctrl: document MB_NODE emulation example on ARM MPAM Fenghua Yu
                   ` (2 subsequent siblings)
  22 siblings, 0 replies; 24+ messages in thread
From: Fenghua Yu @ 2026-07-16 21:03 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
	Shaopeng Tan, Chen Yu, Babu Moger, Drew Fustini, Vikram Sethi,
	Shanker Donthineni, Newton Liu, Gavin Shan
  Cc: linux-kernel, linux-arm-kernel, Fenghua Yu

On ARM MPAM platforms backed by a memory-level MSC, the MB control's
scope reads NODE and schemata identifiers are NUMA node ids rather than
L3 cache ids. Document the scope file values, mon_NODE_XX monitor
directories, and how to interpret MB: lines for both L3-scoped and
NODE-scoped MBA resources in Documentation/filesystems/resctrl.rst.

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

diff --git a/Documentation/filesystems/resctrl.rst b/Documentation/filesystems/resctrl.rst
index 0de86d00c0a6..a88ea9c2a386 100644
--- a/Documentation/filesystems/resctrl.rst
+++ b/Documentation/filesystems/resctrl.rst
@@ -89,9 +89,13 @@ allocation:
 		(for example ``MB`` and ``MB_NODE`` on MBA resources).
 
 		Each control subdirectory is read-only and contains ``scope``
-		and ``type`` files. Bandwidth (scalar) controls also expose a
-		``status`` file that reads ``enabled`` when the control is backed
-		by bandwidth-control hardware and ``disabled`` otherwise.
+		and ``type`` files. The ``scope`` file reads ``L3`` when the
+		control's domain identifiers are L3 cache IDs and ``NODE`` when
+		they are NUMA node IDs. This determines how to interpret numeric
+		IDs in the control's ``schemata`` line and in ``mon_NODE_XX``
+		directories under ``mon_data``. Bandwidth (scalar) controls also
+		expose a ``status`` file that reads ``enabled`` when the control
+		is backed by bandwidth-control hardware and ``disabled`` otherwise.
 
 		On resources that support control emulation (see "MBA control
 		emulation" below), this directory also contains a writable
@@ -656,6 +660,14 @@ When monitoring is enabled all MON groups will also contain:
 	each instance of an L3 cache. Each directory contains files for the enabled
 	L3 events (e.g. "llc_occupancy", "mbm_total_bytes", and "mbm_local_bytes").
 
+	On platforms where memory bandwidth monitoring is associated with the
+	MB resource (for example ARM MPAM systems with a memory-level MSC),
+	there will be a "mon_NODE_XX" directory for each MB monitor domain.
+	"XX" is the NUMA node id that also appears in the "MB:" line of
+	"schemata" when the MB control's ``scope`` reads ``NODE``. 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",
@@ -987,11 +999,27 @@ or
 Memory bandwidth Allocation (default mode)
 ------------------------------------------
 
-Memory b/w domain is L3 cache.
-::
+On most platforms the memory bandwidth (MBA) domain is the L3 cache and
+the numeric identifiers in the "MB:" line are L3 cache ids::
 
 	MB:<cache_id0>=bandwidth0;<cache_id1>=bandwidth1;...
 
+On ARM MPAM platforms backed by a memory-level MSC, the MB control's
+``scope`` reads ``NODE`` and the identifiers are NUMA node ids instead::
+
+	MB:<node_id0>=bandwidth0;<node_id1>=bandwidth1;...
+
+Example on a system where MB domains are NUMA nodes::
+
+	# cat /sys/fs/resctrl/info/MB/resource_schemata/MB/scope
+	NODE
+	# cat schemata
+	MB:0=100;1=100;2=100;10=100
+	L3:1=ffff;2=ffff
+
+Read the MB control's ``scope`` under ``info/MB/resource_schemata/`` before
+interpreting "MB:" entries in "schemata" or directory names under "mon_data".
+
 MBA control emulation
 ---------------------
 Some platforms expose memory bandwidth allocation through a native control
@@ -1062,11 +1090,14 @@ configuration in each group's ``schemata`` file is unaffected.
 Memory bandwidth Allocation specified in MiBps
 ----------------------------------------------
 
-Memory bandwidth domain is L3 cache.
-::
+When MBA domains are L3 caches::
 
 	MB:<cache_id0>=bw_MiBps0;<cache_id1>=bw_MiBps1;...
 
+When the MB control's ``scope`` reads ``NODE``::
+
+	MB:<node_id0>=bw_MiBps0;<node_id1>=bw_MiBps1;...
+
 Slow Memory Bandwidth Allocation (SMBA)
 ---------------------------------------
 AMD hardware supports Slow Memory Bandwidth Allocation (SMBA).
-- 
2.43.0



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

* [PATCH 21/23] Documentation: resctrl: document MB_NODE emulation example on ARM MPAM
  2026-07-16 21:02 [PATCH RFC 00/23] resctrl: MBA control emulation and ARM MPAM MB_NODE support Fenghua Yu
                   ` (19 preceding siblings ...)
  2026-07-16 21:03 ` [PATCH 20/23] Documentation: resctrl: document NODE-scoped MBA domains and mon_NODE monitoring Fenghua Yu
@ 2026-07-16 21:03 ` Fenghua Yu
  2026-07-16 21:03 ` [PATCH 22/23] arm_mpam: Add KUnit test for CPU-less NUMA node affinity Fenghua Yu
  2026-07-16 21:03 ` [PATCH 23/23] selftests/resctrl: Add MB emulation test for ARM MPAM Fenghua Yu
  22 siblings, 0 replies; 24+ messages in thread
From: Fenghua Yu @ 2026-07-16 21:03 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
	Shaopeng Tan, Chen Yu, Babu Moger, Drew Fustini, Vikram Sethi,
	Shanker Donthineni, Newton Liu, Gavin Shan
  Cc: linux-kernel, linux-arm-kernel, Fenghua Yu

Add an ARM MPAM specific example to the generic "MBA control emulation"
section: the concrete MB and MB_NODE controls (their scope, status, and
schemata identifiers), how MB_NODE emulates MB in legacy mode, and the
resource_schemata subdirectory layouts for the enabled/disabled MB
combinations.

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

diff --git a/Documentation/filesystems/resctrl.rst b/Documentation/filesystems/resctrl.rst
index a88ea9c2a386..6027b2f9aa15 100644
--- a/Documentation/filesystems/resctrl.rst
+++ b/Documentation/filesystems/resctrl.rst
@@ -1087,6 +1087,58 @@ down and recreated, so a concurrent reader may briefly observe
 the control subdirectories are informational only and the actual bandwidth
 configuration in each group's ``schemata`` file is unaffected.
 
+The following illustrates one such configuration on ARM MPAM.
+
+ARM MPAM example
+~~~~~~~~~~~~~~~~
+On ARM MPAM systems where the MBA resource is backed by a memory-level
+MSC, the kernel exposes two bandwidth controls:
+
+``MB`` (default):
+	The legacy control. When the L3 cache MSC has usable MBA hardware,
+	this control is backed by cache-level MBW (``mbw_max``) hardware,
+	its ``status`` reads ``enabled``, and its ``scope`` reads ``L3``.
+	The ``MB:`` schemata line uses L3 cache identifiers.
+
+``MB_NODE``:
+	The native, node-scoped control. When the memory MSC has usable MBA
+	hardware, this control is backed by node-level MBW hardware, its
+	``status`` reads ``enabled``, and its ``scope`` reads ``NODE``.
+	The ``MB_NODE:`` schemata line uses NUMA node identifiers.
+
+``MB_NODE`` is the native control that emulates ``MB`` in legacy mode
+when ``MB`` has no MBW hardware of its own.
+
+``MB_NODE`` is only created on a memory-class MBA resource, and such a
+resource is only registered when it has usable MBA hardware, so whenever
+``MB_NODE`` is present its ``status`` reads ``enabled``. (If ``MB_NODE``
+is not supported it is simply absent from ``info/MB/resource_schemata/``.)
+The control subdirectory layout therefore depends only on the ``MB``
+control's ``status`` (the trees below show ``legacy`` mode; in ``native``
+mode every control is a sibling directly under ``resource_schemata/``):
+
+**MB disabled, MB_NODE enabled** (``MB`` emulated by ``MB_NODE``)::
+
+	info/MB/resource_schemata/
+	├── mode
+	└── MB/
+	    ├── scope                     # NODE
+	    ├── status                    # disabled
+	    └── MB_NODE/
+	        ├── scope                 # NODE
+	        └── status                # enabled
+
+**MB enabled, MB_NODE enabled**::
+
+	info/MB/resource_schemata/
+	├── mode
+	├── MB/
+	│   ├── scope                     # L3
+	│   └── status                    # enabled
+	└── MB_NODE/
+	    ├── scope                     # NODE
+	    └── status                    # enabled
+
 Memory bandwidth Allocation specified in MiBps
 ----------------------------------------------
 
-- 
2.43.0



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

* [PATCH 22/23] arm_mpam: Add KUnit test for CPU-less NUMA node affinity
  2026-07-16 21:02 [PATCH RFC 00/23] resctrl: MBA control emulation and ARM MPAM MB_NODE support Fenghua Yu
                   ` (20 preceding siblings ...)
  2026-07-16 21:03 ` [PATCH 21/23] Documentation: resctrl: document MB_NODE emulation example on ARM MPAM Fenghua Yu
@ 2026-07-16 21:03 ` Fenghua Yu
  2026-07-16 21:03 ` [PATCH 23/23] selftests/resctrl: Add MB emulation test for ARM MPAM Fenghua Yu
  22 siblings, 0 replies; 24+ messages in thread
From: Fenghua Yu @ 2026-07-16 21:03 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
	Shaopeng Tan, Chen Yu, Babu Moger, Drew Fustini, Vikram Sethi,
	Shanker Donthineni, Newton Liu, Gavin Shan
  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.43.0



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

* [PATCH 23/23] selftests/resctrl: Add MB emulation test for ARM MPAM
  2026-07-16 21:02 [PATCH RFC 00/23] resctrl: MBA control emulation and ARM MPAM MB_NODE support Fenghua Yu
                   ` (21 preceding siblings ...)
  2026-07-16 21:03 ` [PATCH 22/23] arm_mpam: Add KUnit test for CPU-less NUMA node affinity Fenghua Yu
@ 2026-07-16 21:03 ` Fenghua Yu
  22 siblings, 0 replies; 24+ messages in thread
From: Fenghua Yu @ 2026-07-16 21:03 UTC (permalink / raw)
  To: Reinette Chatre, Tony Luck, Ben Horgan, James Morse, Dave Martin,
	Shaopeng Tan, Chen Yu, Babu Moger, Drew Fustini, Vikram Sethi,
	Shanker Donthineni, Newton Liu, Gavin Shan
  Cc: linux-kernel, linux-arm-kernel, Fenghua Yu

Add a kselftest that exercises MB control emulation on ARM MPAM:
info/MB/resource_schemata hierarchy (nested MB_NODE under MB in legacy
mode, flat sibling layout in native mode), legacy/native mode switching,
and schemata visibility/mirroring when the default MB control is disabled.

Signed-off-by: Fenghua Yu <fenghuay@nvidia.com>
---
 tools/testing/selftests/resctrl/config        |   1 +
 .../selftests/resctrl/mb_emulation_test.c     | 460 ++++++++++++++++++
 tools/testing/selftests/resctrl/resctrl.h     |   1 +
 .../testing/selftests/resctrl/resctrl_tests.c |   1 +
 4 files changed, 463 insertions(+)
 create mode 100644 tools/testing/selftests/resctrl/mb_emulation_test.c

diff --git a/tools/testing/selftests/resctrl/config b/tools/testing/selftests/resctrl/config
index 8d9f2deb56ed..85e4f3fc647f 100644
--- a/tools/testing/selftests/resctrl/config
+++ b/tools/testing/selftests/resctrl/config
@@ -1,2 +1,3 @@
 CONFIG_X86_CPU_RESCTRL=y
 CONFIG_PROC_CPU_RESCTRL=y
+CONFIG_ARM64_MPAM_RESCTRL_FS=y
diff --git a/tools/testing/selftests/resctrl/mb_emulation_test.c b/tools/testing/selftests/resctrl/mb_emulation_test.c
new file mode 100644
index 000000000000..0fa88b43f89f
--- /dev/null
+++ b/tools/testing/selftests/resctrl/mb_emulation_test.c
@@ -0,0 +1,460 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * MB (memory bandwidth) control emulation test (ARM MPAM)
+ *
+ * Exercises info/MB/resource_schemata/ layout, legacy/native mode switching,
+ * and schemata visibility for emulated MB/MB_NODE controls.
+ */
+#include <fcntl.h>
+#include <limits.h>
+
+#include "resctrl.h"
+
+#define MB_INFO			INFO_PATH "/MB"
+#define MB_SCHEMATA_DIR		MB_INFO "/resource_schemata"
+#define MB_MODE_PATH		MB_SCHEMATA_DIR "/mode"
+#define MB_CTRL_PATH		MB_SCHEMATA_DIR "/MB"
+#define MB_NODE_CTRL_PATH	MB_SCHEMATA_DIR "/MB_NODE"
+#define MB_NODE_NESTED_PATH	MB_CTRL_PATH "/MB_NODE"
+#define ROOT_SCHEMATA_PATH	RESCTRL_PATH "/schemata"
+
+struct mb_ctrl_paths {
+	char mb_status[PATH_MAX];
+	char mb_node_status[PATH_MAX];
+	bool mb_node_nested;
+};
+
+static int read_file(const char *path, char *buf, size_t buflen)
+{
+	int fd, n;
+	ssize_t ret;
+
+	fd = open(path, O_RDONLY);
+	if (fd < 0)
+		return -errno;
+
+	ret = read(fd, buf, buflen - 1);
+	close(fd);
+	if (ret < 0)
+		return -errno;
+
+	n = (int)ret;
+	while (n > 0 && (buf[n - 1] == '\n' || buf[n - 1] == ' '))
+		n--;
+	buf[n] = '\0';
+
+	return 0;
+}
+
+static bool path_is_dir(const char *path)
+{
+	struct stat st;
+
+	if (stat(path, &st))
+		return false;
+
+	return S_ISDIR(st.st_mode);
+}
+
+static int mb_resolve_ctrl_paths(struct mb_ctrl_paths *paths)
+{
+	if (path_is_dir(MB_NODE_NESTED_PATH)) {
+		snprintf(paths->mb_status, sizeof(paths->mb_status),
+			 "%s/status", MB_CTRL_PATH);
+		snprintf(paths->mb_node_status, sizeof(paths->mb_node_status),
+			 "%s/status", MB_NODE_NESTED_PATH);
+		paths->mb_node_nested = true;
+		return 0;
+	}
+
+	if (path_is_dir(MB_NODE_CTRL_PATH)) {
+		snprintf(paths->mb_status, sizeof(paths->mb_status),
+			 "%s/status", MB_CTRL_PATH);
+		snprintf(paths->mb_node_status, sizeof(paths->mb_node_status),
+			 "%s/status", MB_NODE_CTRL_PATH);
+		paths->mb_node_nested = false;
+		return 0;
+	}
+
+	return -ENOENT;
+}
+
+static int mb_read_status(const char *path, char *status, size_t len)
+{
+	char buf[32];
+	int ret;
+
+	ret = read_file(path, buf, sizeof(buf));
+	if (ret)
+		return ret;
+
+	if (!strcmp(buf, "enabled") || !strcmp(buf, "disabled")) {
+		strncpy(status, buf, len);
+		status[len - 1] = '\0';
+		return 0;
+	}
+
+	ksft_print_msg("unexpected status in %s: '%s'\n", path, buf);
+	return -EINVAL;
+}
+
+static int mb_read_mode(char *buf, size_t len)
+{
+	return read_file(MB_MODE_PATH, buf, len);
+}
+
+static int mb_write_mode(const char *mode)
+{
+	char msg[32];
+	int fd, ret, len;
+
+	len = snprintf(msg, sizeof(msg), "%s\n", mode);
+	if (len < 0 || len >= (int)sizeof(msg))
+		return -EINVAL;
+
+	fd = open(MB_MODE_PATH, O_WRONLY);
+	if (fd < 0)
+		return -errno;
+
+	ret = write(fd, msg, len) == len ? 0 : -errno;
+	close(fd);
+
+	return ret;
+}
+
+static bool mb_mode_active_is(const char *mode_text, const char *mode)
+{
+	char pattern[32];
+
+	snprintf(pattern, sizeof(pattern), "[%s]", mode);
+	return strstr(mode_text, pattern) != NULL;
+}
+
+static int mb_read_root_schemata(char *buf, size_t len)
+{
+	return read_file(ROOT_SCHEMATA_PATH, buf, len);
+}
+
+/*
+ * resctrl right-aligns schemata names with leading whitespace, so a control
+ * line looks like "             MB:0=100...". Treat prefix as matching when it
+ * appears at the start of a line, ignoring any leading spaces or tabs.
+ */
+static const char *schemata_find_line(const char *schemata, const char *prefix)
+{
+	const char *p;
+
+	if (!schemata || !prefix)
+		return NULL;
+
+	for (p = strstr(schemata, prefix); p; p = strstr(p + 1, prefix)) {
+		const char *q = p;
+
+		while (q > schemata && (q[-1] == ' ' || q[-1] == '\t'))
+			q--;
+
+		if (q == schemata || q[-1] == '\n')
+			return p;
+	}
+
+	return NULL;
+}
+
+static bool schemata_has_prefix(const char *schemata, const char *prefix)
+{
+	return schemata_find_line(schemata, prefix) != NULL;
+}
+
+/*
+ * Copy the value portion (everything after the first '=') of the schemata line
+ * that starts with @prefix into @out, stopping at the end of that line.
+ */
+static int schemata_line_value(const char *schemata, const char *prefix,
+			       char *out, size_t len)
+{
+	const char *line, *eq, *end;
+	size_t n;
+
+	line = schemata_find_line(schemata, prefix);
+	if (!line)
+		return -ENOENT;
+
+	eq = strchr(line, '=');
+	if (!eq)
+		return -EINVAL;
+
+	eq++;
+	end = strchr(eq, '\n');
+	n = end ? (size_t)(end - eq) : strlen(eq);
+	if (n >= len)
+		return -ENOSPC;
+
+	memcpy(out, eq, n);
+	out[n] = '\0';
+
+	return 0;
+}
+
+static int mb_expect_hierarchy(bool nested, const char *why)
+{
+	bool have_nested = path_is_dir(MB_NODE_NESTED_PATH);
+	bool have_sibling = path_is_dir(MB_NODE_CTRL_PATH);
+
+	if (nested) {
+		if (!have_nested || have_sibling) {
+			ksft_print_msg("%s: expected MB_NODE nested, nested=%d sibling=%d\n",
+					 why, have_nested, have_sibling);
+			return -EINVAL;
+		}
+		return 0;
+	}
+
+	if (!have_sibling || have_nested) {
+		ksft_print_msg("%s: expected MB_NODE sibling of MB, nested=%d sibling=%d\n",
+				 why, have_nested, have_sibling);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int mb_check_layout(const char *mode, const struct mb_ctrl_paths *paths)
+{
+	char mb_status[16];
+	bool native = !strcmp(mode, "native");
+	bool expect_nested;
+	int ret;
+
+	if (mb_read_status(paths->mb_status, mb_status, sizeof(mb_status)))
+		return -errno;
+
+	expect_nested = !native && !strcmp(mb_status, "disabled");
+
+	ret = mb_expect_hierarchy(expect_nested, mode);
+	if (ret)
+		return ret;
+
+	ksft_print_msg("switched to %s: hierarchy=%s\n", mode,
+		       expect_nested ? "nested (MB_NODE under MB)" :
+				       "flat (MB_NODE sibling of MB)");
+	return 0;
+}
+
+static int mb_test_mode_read(void)
+{
+	char mode[64];
+
+	if (mb_read_mode(mode, sizeof(mode)))
+		return -errno;
+
+	if (!mb_mode_active_is(mode, "legacy") && !mb_mode_active_is(mode, "native")) {
+		ksft_print_msg("mode file has no active mode: '%s'\n", mode);
+		return -EINVAL;
+	}
+
+	ksft_print_msg("mode file: %s\n", mode);
+	return 0;
+}
+
+static int mb_test_schemata_visibility(bool expect_mb_line, const char *mode)
+{
+	char schemata[4096];
+	bool has_mb, has_mb_node;
+
+	if (mb_read_root_schemata(schemata, sizeof(schemata)))
+		return -errno;
+
+	has_mb = schemata_has_prefix(schemata, "MB:");
+	has_mb_node = schemata_has_prefix(schemata, "MB_NODE:");
+
+	if (expect_mb_line && !has_mb) {
+		ksft_print_msg("%s: expected MB: line in schemata\n", mode);
+		return -EINVAL;
+	}
+	if (!expect_mb_line && has_mb) {
+		ksft_print_msg("%s: MB: line should be hidden in schemata\n", mode);
+		return -EINVAL;
+	}
+	if (!has_mb_node) {
+		ksft_print_msg("%s: expected MB_NODE: line in schemata\n", mode);
+		return -EINVAL;
+	}
+
+	ksft_print_msg("switched to %s: schemata MB:%d MB_NODE:%d\n",
+		       mode, has_mb, has_mb_node);
+	return 0;
+}
+
+static int mb_test_schemata_mirror_legacy(void)
+{
+	char schemata[4096];
+	char mb_val[1024];
+	char node_val[1024];
+
+	if (mb_read_root_schemata(schemata, sizeof(schemata)))
+		return -errno;
+
+	if (schemata_line_value(schemata, "MB:", mb_val, sizeof(mb_val)) ||
+	    schemata_line_value(schemata, "MB_NODE:", node_val, sizeof(node_val))) {
+		ksft_print_msg("legacy mirror: missing MB: or MB_NODE: line\n");
+		return -EINVAL;
+	}
+
+	if (strcmp(mb_val, node_val)) {
+		ksft_print_msg("legacy mirror: MB:%s != MB_NODE:%s\n", mb_val, node_val);
+		return -EINVAL;
+	}
+
+	ksft_print_msg("switched to legacy: MB mirrors MB_NODE as '%s'\n", mb_val);
+	return 0;
+}
+
+static int mb_restore_mode(const char *orig_mode)
+{
+	if (mb_mode_active_is(orig_mode, "legacy"))
+		return mb_write_mode("legacy");
+	if (mb_mode_active_is(orig_mode, "native"))
+		return mb_write_mode("native");
+
+	return 0;
+}
+
+/* Confirm the mode file reports @mode as the active ([bracketed]) selection. */
+static int mb_verify_active_mode(const char *mode)
+{
+	char buf[64];
+
+	if (mb_read_mode(buf, sizeof(buf)))
+		return -errno;
+
+	if (!mb_mode_active_is(buf, mode)) {
+		ksft_print_msg("mode switch to %s failed, mode file: '%s'\n",
+			       mode, buf);
+		return -EINVAL;
+	}
+
+	ksft_print_msg("switched to %s: mode file='%s'\n", mode, buf);
+	return 0;
+}
+
+/*
+ * Switch to @mode and verify the mode file, the info/ resource_schemata
+ * hierarchy, and root schemata visibility all reflect the new mode.
+ */
+static int mb_switch_and_check(const char *mode, const struct mb_ctrl_paths *paths,
+			       bool mb_disabled)
+{
+	bool legacy = !strcmp(mode, "legacy");
+	int ret;
+
+	ksft_print_msg("--- switching to %s mode ---\n", mode);
+
+	ret = mb_write_mode(mode);
+	if (ret) {
+		ksft_print_msg("failed to write %s mode\n", mode);
+		return ret;
+	}
+
+	ret = mb_verify_active_mode(mode);
+	if (ret)
+		return ret;
+
+	ret = mb_check_layout(mode, paths);
+	if (ret)
+		return ret;
+
+	if (!mb_disabled) {
+		ksft_print_msg("switched to %s: schemata checks skipped (MB enabled)\n",
+			       mode);
+		return 0;
+	}
+
+	ret = mb_test_schemata_visibility(legacy, mode);
+	if (ret)
+		return ret;
+
+	if (legacy)
+		ret = mb_test_schemata_mirror_legacy();
+
+	return ret;
+}
+
+static int mb_emulation_run_test(const struct resctrl_test *test,
+				 const struct user_params *uparams)
+{
+	struct mb_ctrl_paths paths;
+	char orig_mode[64];
+	char mb_status[16];
+	bool mb_disabled;
+	int ret;
+
+	(void)test;
+	(void)uparams;
+
+	if (mb_resolve_ctrl_paths(&paths)) {
+		ksft_print_msg("MB_NODE control not found under resource_schemata\n");
+		return -ENOENT;
+	}
+
+	if (mb_read_mode(orig_mode, sizeof(orig_mode))) {
+		ksft_print_msg("failed to read %s\n", MB_MODE_PATH);
+		return -errno;
+	}
+
+	if (mb_read_status(paths.mb_status, mb_status, sizeof(mb_status))) {
+		ksft_print_msg("failed to read %s\n", paths.mb_status);
+		return -errno;
+	}
+
+	ksft_print_msg("MB status=%s MB_NODE nested=%d (initial)\n",
+		       mb_status, paths.mb_node_nested);
+
+	ret = mb_test_mode_read();
+	if (ret)
+		goto out_restore;
+
+	mb_disabled = !strcmp(mb_status, "disabled");
+
+	/* Cycle legacy -> native -> legacy, checking each transition. */
+	ret = mb_switch_and_check("legacy", &paths, mb_disabled);
+	if (ret)
+		goto out_restore;
+
+	ret = mb_switch_and_check("native", &paths, mb_disabled);
+	if (ret)
+		goto out_restore;
+
+	ret = mb_switch_and_check("legacy", &paths, mb_disabled);
+	if (ret)
+		goto out_restore;
+
+out_restore:
+	if (mb_restore_mode(orig_mode))
+		ksft_print_msg("warning: failed to restore original mode\n");
+
+	return ret;
+}
+
+static bool mb_emulation_feature_check(const struct resctrl_test *test)
+{
+	(void)test;
+
+	if (!resctrl_resource_exists("MB"))
+		return false;
+
+	if (!path_is_dir(MB_SCHEMATA_DIR))
+		return false;
+
+	if (!resource_info_file_exists("MB", "resource_schemata/mode"))
+		return false;
+
+	return path_is_dir(MB_NODE_CTRL_PATH) || path_is_dir(MB_NODE_NESTED_PATH);
+}
+
+struct resctrl_test mb_emulation_test = {
+	.name = "mb_emulation",
+	.group = "mb",
+	.resource = "MB",
+	.feature_check = mb_emulation_feature_check,
+	.run_test = mb_emulation_run_test,
+};
diff --git a/tools/testing/selftests/resctrl/resctrl.h b/tools/testing/selftests/resctrl/resctrl.h
index 175101022bf3..ad1c17c0b0bf 100644
--- a/tools/testing/selftests/resctrl/resctrl.h
+++ b/tools/testing/selftests/resctrl/resctrl.h
@@ -242,5 +242,6 @@ extern struct resctrl_test cmt_test;
 extern struct resctrl_test l3_cat_test;
 extern struct resctrl_test l3_noncont_cat_test;
 extern struct resctrl_test l2_noncont_cat_test;
+extern struct resctrl_test mb_emulation_test;
 
 #endif /* RESCTRL_H */
diff --git a/tools/testing/selftests/resctrl/resctrl_tests.c b/tools/testing/selftests/resctrl/resctrl_tests.c
index dbcd5eea9fbc..593f0ca5251b 100644
--- a/tools/testing/selftests/resctrl/resctrl_tests.c
+++ b/tools/testing/selftests/resctrl/resctrl_tests.c
@@ -17,6 +17,7 @@ volatile int *value_sink = &sink_target;
 static struct resctrl_test *resctrl_tests[] = {
 	&mbm_test,
 	&mba_test,
+	&mb_emulation_test,
 	&cmt_test,
 	&l3_cat_test,
 	&l3_noncont_cat_test,
-- 
2.43.0



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

end of thread, other threads:[~2026-07-16 21:28 UTC | newest]

Thread overview: 24+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-16 21:02 [PATCH RFC 00/23] resctrl: MBA control emulation and ARM MPAM MB_NODE support Fenghua Yu
2026-07-16 21:02 ` [PATCH 01/23] resctrl: Fix ownership of resource_schemata control subdirectories Fenghua Yu
2026-07-16 21:02 ` [PATCH 02/23] arm_mpam: Fix NULL address access issue Fenghua Yu
2026-07-16 21:02 ` [PATCH 03/23] resctrl: Expose MBA resource_schemata mode sysfs Fenghua Yu
2026-07-16 21:02 ` [PATCH 04/23] resctrl: Expose per-control status in resource_schemata Fenghua Yu
2026-07-16 21:02 ` [PATCH 05/23] resctrl: Add nested resource_schemata support for emulated controls Fenghua Yu
2026-07-16 21:02 ` [PATCH 06/23] resctrl: Mirror schemata for controls without MBW hardware Fenghua Yu
2026-07-16 21:02 ` [PATCH 07/23] resctrl: Rebuild resource_schemata subdirs on MBA mode change Fenghua Yu
2026-07-16 21:02 ` [PATCH 08/23] Documentation: resctrl: document MBA control emulation Fenghua Yu
2026-07-16 21:02 ` [PATCH 09/23] resctrl: De-hardcode L3 monitor infrastructure Fenghua Yu
2026-07-16 21:03 ` [PATCH 10/23] resctrl: Expose MBA MBM counter assignment sysfs Fenghua Yu
2026-07-16 21:03 ` [PATCH 11/23] resctrl: name node-scoped monitor domains mon_NODE_<id> Fenghua Yu
2026-07-16 21:03 ` [PATCH 12/23] resctrl: Add node-scope MBM total event Fenghua Yu
2026-07-16 21:03 ` [PATCH 13/23] resctrl: Make MBM paths resource-aware Fenghua Yu
2026-07-16 21:03 ` [PATCH 14/23] arm_mpam: Support memory-level MSCs and ABMC per class Fenghua Yu
2026-07-16 21:03 ` [PATCH 15/23] arm_mpam: Refine L3 topology and class selection Fenghua Yu
2026-07-16 21:03 ` [PATCH 16/23] arm_mpam: Include all MSC components during domain setup Fenghua Yu
2026-07-16 21:03 ` [PATCH 17/23] arm_mpam: Handle CPU-less numa nodes Fenghua Yu
2026-07-16 21:03 ` [PATCH 18/23] arm_mpam: Emulate MB control with node-scoped MB_NODE control Fenghua Yu
2026-07-16 21:03 ` [PATCH 19/23] Documentation: arm64: mpam: document memory-level MB control and NUMA nodes Fenghua Yu
2026-07-16 21:03 ` [PATCH 20/23] Documentation: resctrl: document NODE-scoped MBA domains and mon_NODE monitoring Fenghua Yu
2026-07-16 21:03 ` [PATCH 21/23] Documentation: resctrl: document MB_NODE emulation example on ARM MPAM Fenghua Yu
2026-07-16 21:03 ` [PATCH 22/23] arm_mpam: Add KUnit test for CPU-less NUMA node affinity Fenghua Yu
2026-07-16 21:03 ` [PATCH 23/23] selftests/resctrl: Add MB emulation test for ARM MPAM Fenghua Yu

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