* [PATCH v10 RESEND 00/15] arm_mpam: Introduce Narrow-PARTID feature
@ 2026-10-20 13:42 Jinjie Ruan
2026-10-20 13:42 ` [PATCH v10 RESEND 01/15] fs/resctrl: Return -EBUSY when per-CLOSID RMIDs are in limbo Jinjie Ruan
` (14 more replies)
0 siblings, 15 replies; 22+ messages in thread
From: Jinjie Ruan @ 2026-10-20 13:42 UTC (permalink / raw)
To: tony.luck, reinette.chatre, Dave.Martin, james.morse, babu.moger,
tglx, mingo, bp, dave.hansen, hpa, ben.horgan, fenghuay,
ruanjinjie, tan.shaopeng, zengheng4, jic23, gshan,
catalin.marinas, x86, linux-kernel
Background
==========
In ARM MPAM, a monitoring group is defined by a (PARTID, PMG) pair.
However, many production platforms implement only a single PMG bit,
limiting the total number of monitoring groups long before the request
PARTID space is exhausted.
Hardware Mechanism
===================
Platforms with "PARTID narrowing" mitigate this by mapping multiple
request PARTIDs onto a single internal PARTID. The internal PARTID
dictates the control group configuration, while each individual request
PARTID functions as an independent monitoring group. This decouples
control and monitoring allocation, allowing more monitoring groups
than the PMG constraint normally permits.
Software Strategy
==================
Using request PARTIDs beyond the internal range is only safe when no MPAM
class loses a non-aliasing control. While aliasing controls (e.g., cache
portion bitmaps) can be safely replicated across request PARTIDs,
non-aliasing controls (e.g., cache capacity) are additive and cannot.
To ensure safety, this series implements a completely static grouping
policy evaluated once before resctrl_init(). If any class contains
non-aliasing controls without narrowing capabilities, the expansion
is bypassed. This infrastructure remains completely transparent
to user-space.
Patch Architecture
====================
- Patches 1-6: bugfixes found by sashiko or while testing this series
- Patches 7-9: Preparatory helpers, no functional change
- Patch 10: Track internal and request PARTID limits per class
- Patches 11-12: Driver-side modulo interleaving + flat global RMID
- Patches 13-14: Identify non-aliasing controls; enable the narrowing
- Patch 15: KUnit tests for the narrow-PARTID helpers
Testing
========
Verified on both QEMU and real Hisi hardware:
- Narrowing expands monitoring groups from 4 to 256:
[ 62.602939] mpam:mpam_resctrl_configure_partids: PARTIDs: monitor=256 closid=32 extra=1
[ 63.788467] MPAM enabled with 256 PARTIDs and 4 PMGs
- KUnit: narrow-PARTID helper tests pass.
QEMU branch: https://github.com/ruanjinjie-eng/qemu/tree/mpam
Test cases: https://github.com/ruanjinjie-eng/mpam_testcase
v10 -> v10 RESNED:
- Resend because of SMTP rate limiting.
Changes since v9
- Compatibility with Narrow PARTID and CDP mode
- Fix several sashiko reported issues:
- Return -EBUSY when a CLOSID's RMIDs are only in limbo
- Skip the monitor-context allocation on the force-free limbo path
- Return an correct error pointer from resctrl_arch_mon_ctx_alloc()
on failure.
- Widen the PARTID reset loop counter to u32 to fix the 65536 wrap.
- Fix MPAM enable issue when a MSC fails to probe (e.g. nr_cpus= clipping
the possible CPUs).
- Add KUnit tests for the narrow-PARTID helpers.
- Link to v9: https://lore.kernel.org/all/20261012082337.822258-1-ruanjinjie@huawei.com/
Link: https://sashiko.dev/#/patchset/20261012082337.822258-1-ruanjinjie%40huawei.com
Previous Versions
=================
v9: https://lore.kernel.org/all/20261012082337.822258-1-ruanjinjie@huawei.com/
v8: https://lore.kernel.org/all/20260413085405.1166412-1-zengheng4@huawei.com/
v7: https://lore.kernel.org/all/20260317132141.1272506-1-zengheng4@huawei.com/
v6: https://lore.kernel.org/all/20250222112448.2438586-1-zengheng4@huawei.com/
v5: https://lore.kernel.org/all/20250217031852.2014939-1-zengheng4@huawei.com/
RFC-v4: https://lore.kernel.org/all/20250104101224.873926-1-zengheng4@huawei.com/
RFC-v3: https://lore.kernel.org/all/20241207092136.2488426-1-zengheng4@huawei.com/
RFC-v2: https://lore.kernel.org/all/20241119135104.595630-1-zengheng4@huawei.com/
RFC-v1: https://lore.kernel.org/all/20241114135037.918470-1-zengheng4@huawei.com/
Jinjie Ruan (13):
fs/resctrl: Return -EBUSY when per-CLOSID RMIDs are in limbo
fs/resctrl: Skip monitor context allocation when force-freeing RMIDs
arm_mpam: Fix error return in resctrl_arch_mon_ctx_alloc()
arm_mpam: Fix infinite PARTID reset loop when 65536 PARTIDs are
supported
arm_mpam: Don't stall MPAM enable when an MSC fails to probe
fs/resctrl: Add resctrl_arch_rmid_idx_match() helper
arm_mpam: resctrl: Add resctrl_closid_rmid_to_partid_pmg() helper
arm_mpam: Add a helper to check a PARTID is in a CLOSID
arm_mpam: Track the PARTID and internal PARTID limits per class
arm_mpam: Divide the PARTIDs between the control groups
arm_mpam: Identify the non-aliasing controls
arm_mpam: Use the request PARTIDs beyond the internal range
arm_mpam: resctrl: Add kunit tests for narrow PARTID helpers
Zeng Heng (2):
fs/resctrl: Unmount resctrl with the CDP state it was mounted with
arm_mpam: Map resctrl RMIDs onto the request PARTIDs
arch/x86/include/asm/resctrl.h | 6 +
drivers/resctrl/.kunitconfig | 4 +
drivers/resctrl/mpam_devices.c | 160 ++++++++--
drivers/resctrl/mpam_internal.h | 25 ++
drivers/resctrl/mpam_resctrl.c | 227 +++++++++++---
drivers/resctrl/test_mpam_resctrl.c | 466 ++++++++++++++++++++++++++++
fs/resctrl/monitor.c | 36 ++-
fs/resctrl/rdtgroup.c | 46 ++-
include/linux/arm_mpam.h | 1 +
9 files changed, 889 insertions(+), 82 deletions(-)
create mode 100644 drivers/resctrl/.kunitconfig
--
2.34.1
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v10 RESEND 01/15] fs/resctrl: Return -EBUSY when per-CLOSID RMIDs are in limbo
2026-10-20 13:42 [PATCH v10 RESEND 00/15] arm_mpam: Introduce Narrow-PARTID feature Jinjie Ruan
@ 2026-10-20 13:42 ` Jinjie Ruan
2026-10-26 14:05 ` Ben Horgan
2026-10-20 13:42 ` [PATCH v10 RESEND 02/15] fs/resctrl: Skip monitor context allocation when force-freeing RMIDs Jinjie Ruan
` (13 subsequent siblings)
14 siblings, 1 reply; 22+ messages in thread
From: Jinjie Ruan @ 2026-10-20 13:42 UTC (permalink / raw)
To: tony.luck, reinette.chatre, Dave.Martin, james.morse, babu.moger,
tglx, mingo, bp, dave.hansen, hpa, ben.horgan, fenghuay,
ruanjinjie, tan.shaopeng, zengheng4, jic23, gshan,
catalin.marinas, x86, linux-kernel
resctrl_find_free_rmid() returns ERR_PTR(-ENOSPC) when no matching RMID
is found in the free list. On systems where RMIDs depend on CLOSIDs
(CONFIG_RESCTRL_RMID_DEPENDS_ON_CLOSID), the target CLOSID may have RMIDs
temporarily in limbo rather than permanently exhausted.
When userspace attempts to create a new monitoring group, a permanent
-ENOSPC failure prevents retry even though the CLOSID's RMIDs may become
available once the limbo handler completes.
Check closid_num_dirty_rmid[] before returning -ENOSPC. If the target
CLOSID has dirty RMIDs, return -EBUSY to signal a retryable condition.
Fixes: c4c0376eefe1 ("x86/resctrl: Allow RMID allocation to be scoped by CLOSID")
Cc: James Morse <james.morse@arm.com>
Cc: Reinette Chatre <reinette.chatre@intel.com>
Cc: Babu Moger <babu.moger@amd.com>
Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
---
fs/resctrl/monitor.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/fs/resctrl/monitor.c b/fs/resctrl/monitor.c
index 73413cb128ea..da883adc3adc 100644
--- a/fs/resctrl/monitor.c
+++ b/fs/resctrl/monitor.c
@@ -225,6 +225,14 @@ static struct rmid_entry *resctrl_find_free_rmid(u32 closid)
return itr;
}
+ /*
+ * If this CLOSID has RMIDs in limbo, return -EBUSY so userspace can
+ * retry. Otherwise, the CLOSID is genuinely out of RMIDs.
+ */
+ if (IS_ENABLED(CONFIG_RESCTRL_RMID_DEPENDS_ON_CLOSID) &&
+ closid_num_dirty_rmid[closid])
+ return ERR_PTR(-EBUSY);
+
return ERR_PTR(-ENOSPC);
}
--
2.34.1
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v10 RESEND 02/15] fs/resctrl: Skip monitor context allocation when force-freeing RMIDs
2026-10-20 13:42 [PATCH v10 RESEND 00/15] arm_mpam: Introduce Narrow-PARTID feature Jinjie Ruan
2026-10-20 13:42 ` [PATCH v10 RESEND 01/15] fs/resctrl: Return -EBUSY when per-CLOSID RMIDs are in limbo Jinjie Ruan
@ 2026-10-20 13:42 ` Jinjie Ruan
2026-10-26 14:08 ` Ben Horgan
2026-10-20 13:42 ` [PATCH v10 RESEND 03/15] arm_mpam: Fix error return in resctrl_arch_mon_ctx_alloc() Jinjie Ruan
` (12 subsequent siblings)
14 siblings, 1 reply; 22+ messages in thread
From: Jinjie Ruan @ 2026-10-20 13:42 UTC (permalink / raw)
To: tony.luck, reinette.chatre, Dave.Martin, james.morse, babu.moger,
tglx, mingo, bp, dave.hansen, hpa, ben.horgan, fenghuay,
ruanjinjie, tan.shaopeng, zengheng4, jic23, gshan,
catalin.marinas, x86, linux-kernel
__check_limbo() allocates the architecture monitor context before the
scan loop, even when force_free is set. The force-free path never reads
occupancy and never uses the context.
If the allocation fails, the function returns early and leaves the limbo
list unflushed. Callers such as the unmount path then silently fail to
return the RMIDs to the free list, leaving stale CLOSIDs in limbo that
are re-interpreted with the CDP state of the next mount.
Only allocate and free the monitor context when occupancy will be read.
Fixes: fa7d949337cc ("x86/resctrl: Rename and move rdt files to a separate directory")
Cc: James Morse <james.morse@arm.com>
Cc: Reinette Chatre <reinette.chatre@intel.com>
Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
---
fs/resctrl/monitor.c | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/fs/resctrl/monitor.c b/fs/resctrl/monitor.c
index da883adc3adc..ffa9c67c9f00 100644
--- a/fs/resctrl/monitor.c
+++ b/fs/resctrl/monitor.c
@@ -142,11 +142,13 @@ void __check_limbo(struct rdt_l3_mon_domain *d, bool force_free)
u64 val = 0;
arch_priv = mon_event_all[QOS_L3_OCCUP_EVENT_ID].arch_priv;
- arch_mon_ctx = resctrl_arch_mon_ctx_alloc(r, QOS_L3_OCCUP_EVENT_ID);
- if (IS_ERR(arch_mon_ctx)) {
- pr_warn_ratelimited("Failed to allocate monitor context: %pe",
- arch_mon_ctx);
- return;
+ if (!force_free) {
+ arch_mon_ctx = resctrl_arch_mon_ctx_alloc(r, QOS_L3_OCCUP_EVENT_ID);
+ if (IS_ERR(arch_mon_ctx)) {
+ pr_warn_ratelimited("Failed to allocate monitor context: %pe",
+ arch_mon_ctx);
+ return;
+ }
}
/*
@@ -192,7 +194,8 @@ void __check_limbo(struct rdt_l3_mon_domain *d, bool force_free)
cur_idx = idx + 1;
}
- resctrl_arch_mon_ctx_free(r, QOS_L3_OCCUP_EVENT_ID, arch_mon_ctx);
+ if (!force_free)
+ resctrl_arch_mon_ctx_free(r, QOS_L3_OCCUP_EVENT_ID, arch_mon_ctx);
}
bool has_busy_rmid(struct rdt_l3_mon_domain *d)
--
2.34.1
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v10 RESEND 03/15] arm_mpam: Fix error return in resctrl_arch_mon_ctx_alloc()
2026-10-20 13:42 [PATCH v10 RESEND 00/15] arm_mpam: Introduce Narrow-PARTID feature Jinjie Ruan
2026-10-20 13:42 ` [PATCH v10 RESEND 01/15] fs/resctrl: Return -EBUSY when per-CLOSID RMIDs are in limbo Jinjie Ruan
2026-10-20 13:42 ` [PATCH v10 RESEND 02/15] fs/resctrl: Skip monitor context allocation when force-freeing RMIDs Jinjie Ruan
@ 2026-10-20 13:42 ` Jinjie Ruan
2026-10-26 14:40 ` Ben Horgan
2026-10-20 13:42 ` [PATCH v10 RESEND 04/15] arm_mpam: Fix infinite PARTID reset loop when 65536 PARTIDs are supported Jinjie Ruan
` (11 subsequent siblings)
14 siblings, 1 reply; 22+ messages in thread
From: Jinjie Ruan @ 2026-10-20 13:42 UTC (permalink / raw)
To: tony.luck, reinette.chatre, Dave.Martin, james.morse, babu.moger,
tglx, mingo, bp, dave.hansen, hpa, ben.horgan, fenghuay,
ruanjinjie, tan.shaopeng, zengheng4, jic23, gshan,
catalin.marinas, x86, linux-kernel
resctrl_arch_mon_ctx_alloc() stores the allocation result in a
heap-allocated int and returns the pointer unconditionally. When
allocation fails with anything other than a retryable -ENOSPC (e.g.
-EINVAL, -EOPNOTSUPP, or -ENOSPC with a pending signal), the caller
receives a valid heap pointer that IS_ERR() does not detect.
The caller then treats the error code as a monitor index: hardware
reads use an out-of-range index, and resctrl_arch_mon_ctx_free() hands
it to ida_free(), corrupting the allocator state.
Return ERR_PTR() when the allocation failed.
Fixes: 2a3c79c61539 ("arm_mpam: resctrl: Allow resctrl to allocate monitors")
Cc: James Morse <james.morse@arm.com>
Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
---
drivers/resctrl/mpam_resctrl.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/drivers/resctrl/mpam_resctrl.c b/drivers/resctrl/mpam_resctrl.c
index edd8da460fd7..23a8771ef148 100644
--- a/drivers/resctrl/mpam_resctrl.c
+++ b/drivers/resctrl/mpam_resctrl.c
@@ -413,6 +413,13 @@ void *resctrl_arch_mon_ctx_alloc(struct rdt_resource *r,
} while (*ret == -ENOSPC && !signal_pending(current));
finish_wait(&resctrl_mon_ctx_waiters, &wait);
+ if (*ret < 0) {
+ int err = *ret;
+
+ kfree(ret);
+ return ERR_PTR(err);
+ }
+
return ret;
}
--
2.34.1
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v10 RESEND 04/15] arm_mpam: Fix infinite PARTID reset loop when 65536 PARTIDs are supported
2026-10-20 13:42 [PATCH v10 RESEND 00/15] arm_mpam: Introduce Narrow-PARTID feature Jinjie Ruan
` (2 preceding siblings ...)
2026-10-20 13:42 ` [PATCH v10 RESEND 03/15] arm_mpam: Fix error return in resctrl_arch_mon_ctx_alloc() Jinjie Ruan
@ 2026-10-20 13:42 ` Jinjie Ruan
2026-10-20 13:42 ` [PATCH v10 RESEND 05/15] arm_mpam: Don't stall MPAM enable when an MSC fails to probe Jinjie Ruan
` (10 subsequent siblings)
14 siblings, 0 replies; 22+ messages in thread
From: Jinjie Ruan @ 2026-10-20 13:42 UTC (permalink / raw)
To: tony.luck, reinette.chatre, Dave.Martin, james.morse, babu.moger,
tglx, mingo, bp, dave.hansen, hpa, ben.horgan, fenghuay,
ruanjinjie, tan.shaopeng, zengheng4, jic23, gshan,
catalin.marinas, x86, linux-kernel
mpam_reset_ris() and mpam_reprogram_msc walks the PARTID range with
a u16 counter bounded by mpam_partid_max, which can be 65535 on
hardware implementing the full 16-bit PARTID space. After the final
iteration the counter wraps back to zero and the loop never terminates.
Widen the counter to u32.
Fixes: 09b89d2a72f3 ("arm_mpam: Allow configuration to be applied and restored during cpu online")
Fixes: f188a36ca241 ("arm_mpam: Reset MSC controls from cpuhp callbacks")
Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
---
drivers/resctrl/mpam_devices.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
index dd422c56fbb1..61cbc75625fa 100644
--- a/drivers/resctrl/mpam_devices.c
+++ b/drivers/resctrl/mpam_devices.c
@@ -1720,7 +1720,8 @@ static int mpam_save_mbwu_state(void *arg)
*/
static int mpam_reset_ris(void *arg)
{
- u16 partid, partid_max;
+ u16 partid_max;
+ u32 partid;
struct mpam_config reset_cfg = {};
struct mpam_msc_ris *ris = arg;
@@ -1777,7 +1778,7 @@ static int __write_config(void *arg)
static void mpam_reprogram_msc(struct mpam_msc *msc)
{
- u16 partid;
+ u32 partid;
bool reset;
struct mpam_config *cfg;
struct mpam_msc_ris *ris;
--
2.34.1
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v10 RESEND 05/15] arm_mpam: Don't stall MPAM enable when an MSC fails to probe
2026-10-20 13:42 [PATCH v10 RESEND 00/15] arm_mpam: Introduce Narrow-PARTID feature Jinjie Ruan
` (3 preceding siblings ...)
2026-10-20 13:42 ` [PATCH v10 RESEND 04/15] arm_mpam: Fix infinite PARTID reset loop when 65536 PARTIDs are supported Jinjie Ruan
@ 2026-10-20 13:42 ` Jinjie Ruan
2026-10-26 14:49 ` Ben Horgan
2026-10-20 13:42 ` [PATCH v10 RESEND 06/15] fs/resctrl: Unmount resctrl with the CDP state it was mounted with Jinjie Ruan
` (9 subsequent siblings)
14 siblings, 1 reply; 22+ messages in thread
From: Jinjie Ruan @ 2026-10-20 13:42 UTC (permalink / raw)
To: tony.luck, reinette.chatre, Dave.Martin, james.morse, babu.moger,
tglx, mingo, bp, dave.hansen, hpa, ben.horgan, fenghuay,
ruanjinjie, tan.shaopeng, zengheng4, jic23, gshan,
catalin.marinas, x86, linux-kernel
mpam_msc_drv_probe() registers the discovery cpuhp callbacks only when
the number of successfully probed MSCs reaches fw_num_msc. Every failure
path returns before the counter is incremented, and a failed probe is
final - the driver core does not retry. One failing MSC therefore leaves
the counter below fw_num_msc forever: the discovery callbacks are never
registered, mpam_enable() is never scheduled, and MPAM stays dead
system-wide - the resctrl filesystem is never registered, and no error
message hints at the cause.
An MSC can legitimately fail to probe on an otherwise working system:
firmware may describe MSCs that are not accessible from any possible
CPU, e.g. when the possible CPUs are restricted with nr_cpus=. The MSCs
of the clipped CPUs then fail with "MSC is not accessible from any
CPU!", or their cache resources become unresolvable from PPTT.
Count every completed probe, successful or not. Failed MSCs are not on
mpam_all_msc (either never added, or removed by mpam_msc_drv_remove()),
so the discovery path skips them naturally, and MPAM is enabled for the
surviving devices.
Cc: stable@vger.kernel.org
Cc: Ben Horgan <ben.horgan@arm.com>
Cc: Reinette Chatre <reinette.chatre@intel.com>
Cc: Fenghua Yu <fenghuay@nvidia.com>
Cc: Shaopeng Tan <tan.shaopeng@jp.fujitsu.com>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Fixes: f04046f2577a5 ("arm_mpam: Add probe/remove for mpam msc driver and kbuild boiler plate")
Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
---
drivers/resctrl/mpam_devices.c | 26 ++++++++++++++++++--------
1 file changed, 18 insertions(+), 8 deletions(-)
diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
index 61cbc75625fa..a44d66a5733d 100644
--- a/drivers/resctrl/mpam_devices.c
+++ b/drivers/resctrl/mpam_devices.c
@@ -2120,21 +2120,31 @@ static int mpam_msc_drv_probe(struct platform_device *pdev)
msc = do_mpam_msc_drv_probe(pdev);
mutex_unlock(&mpam_list_lock);
- if (IS_ERR(msc))
- return PTR_ERR(msc);
+ if (IS_ERR(msc)) {
+ err = PTR_ERR(msc);
+ goto out;
+ }
/* Create RIS entries described by firmware */
err = acpi_mpam_parse_resources(msc, plat_data);
- if (err) {
+ if (err)
mpam_msc_drv_remove(pdev);
- return err;
- }
-
- if (atomic_add_return(1, &mpam_num_msc) == fw_num_msc)
+out:
+ /*
+ * A failed MSC must not prevent MPAM being enabled for the
+ * surviving devices. Firmware may describe MSCs that are not
+ * accessible from any possible CPU, e.g. when the possible CPUs
+ * were restricted by nr_cpus=. The failed device is not
+ * re-probed, so count it here too: otherwise the counter never
+ * reaches fw_num_msc, the discovery cpuhp callbacks are never
+ * registered and mpam_enable() is never scheduled - MPAM stays
+ * dead system-wide, without any error message.
+ */
+ if (atomic_inc_return(&mpam_num_msc) == fw_num_msc)
mpam_register_cpuhp_callbacks(mpam_discovery_cpu_online, NULL,
"mpam:drv_probe");
- return 0;
+ return err;
}
static struct platform_driver mpam_msc_driver = {
--
2.34.1
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v10 RESEND 06/15] fs/resctrl: Unmount resctrl with the CDP state it was mounted with
2026-10-20 13:42 [PATCH v10 RESEND 00/15] arm_mpam: Introduce Narrow-PARTID feature Jinjie Ruan
` (4 preceding siblings ...)
2026-10-20 13:42 ` [PATCH v10 RESEND 05/15] arm_mpam: Don't stall MPAM enable when an MSC fails to probe Jinjie Ruan
@ 2026-10-20 13:42 ` Jinjie Ruan
2026-10-26 15:08 ` Ben Horgan
2026-10-20 13:42 ` [PATCH v10 RESEND 07/15] fs/resctrl: Add resctrl_arch_rmid_idx_match() helper Jinjie Ruan
` (8 subsequent siblings)
14 siblings, 1 reply; 22+ messages in thread
From: Jinjie Ruan @ 2026-10-20 13:42 UTC (permalink / raw)
To: tony.luck, reinette.chatre, Dave.Martin, james.morse, babu.moger,
tglx, mingo, bp, dave.hansen, hpa, ben.horgan, fenghuay,
ruanjinjie, tan.shaopeng, zengheng4, jic23, gshan,
catalin.marinas, x86, linux-kernel
From: Zeng Heng <zengheng4@huawei.com>
resctrl disables CDP before the control and monitor groups are torn down
on unmount. The architecture translates between CLOSID and PARTID using
the CDP state, so the RMIDs freed by rmdir_all_sub() and
resctrl_fs_teardown() are parsed with the wrong mapping. On arm64 the
RMID limbo handler can still be running, and a CLOSID left in the limbo
list is re-interpreted with the CDP state of the next mount. Enabling
CDP doubles the CLOSID on arm64, which can push the stale value out of
range and take an MPAM error interrupt.
Disable CDP after the groups have been torn down, so every CLOSID freed
on unmount is parsed with the CDP state it was allocated under. Return
the RMIDs still in limbo to the free list and stop the worker, so
nothing carries over to the next mount.
Cc: Tony Luck <tony.luck@intel.com>
Cc: Reinette Chatre <reinette.chatre@intel.com>
Cc: Dave Martin <Dave.Martin@arm.com>
Cc: James Morse <james.morse@arm.com>
Cc: Babu Moger <babu.moger@amd.com>
Signed-off-by: Zeng Heng <zengheng4@huawei.com>
Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
---
fs/resctrl/rdtgroup.c | 46 +++++++++++++++++++++++++++++++++++++++++--
1 file changed, 44 insertions(+), 2 deletions(-)
diff --git a/fs/resctrl/rdtgroup.c b/fs/resctrl/rdtgroup.c
index 5dcbb0a964e8..40821cf07649 100644
--- a/fs/resctrl/rdtgroup.c
+++ b/fs/resctrl/rdtgroup.c
@@ -3145,6 +3145,41 @@ static void resctrl_fs_teardown(void)
rdtgroup_destroy_root();
}
+/*
+ * The RMID limbo handler keeps running after unmount. Any CLOSID still in
+ * limbo would be re-interpreted with the CDP state of the next mount. MPAM
+ * doubles CLOSID when CDP is enabled, which can push the stale value out of
+ * range. Force the limbo list back to the free list before the CLOSID state
+ * is torn down.
+ */
+static void rdt_flush_limbo(void)
+{
+ struct rdt_resource *r = resctrl_arch_get_resource(RDT_RESOURCE_L3);
+ struct rdt_l3_mon_domain *d;
+
+ if (!IS_ENABLED(CONFIG_RESCTRL_RMID_DEPENDS_ON_CLOSID))
+ return;
+
+ if (!resctrl_is_mon_event_enabled(QOS_L3_OCCUP_EVENT_ID))
+ return;
+
+ list_for_each_entry(d, &r->mon_domains, hdr.list) {
+ if (!has_busy_rmid(d))
+ continue;
+
+ /*
+ * The limbo handler acquires rdtgroup_mutex. Release it
+ * here to allow the handler to complete, then reacquire
+ * to maintain lock ordering for __check_limbo().
+ * This matches the pattern used in resctrl_offline_cpu().
+ */
+ mutex_unlock(&rdtgroup_mutex);
+ cancel_delayed_work_sync(&d->cqm_limbo);
+ mutex_lock(&rdtgroup_mutex);
+ __check_limbo(d, true);
+ }
+}
+
static void resctrl_unmount(void)
{
struct rdt_resource *r;
@@ -3152,13 +3187,20 @@ static void resctrl_unmount(void)
cpus_read_lock();
mutex_lock(&rdtgroup_mutex);
- rdt_disable_ctx();
-
/* Put everything back to default values. */
for_each_alloc_capable_rdt_resource(r)
resctrl_arch_reset_all_ctrls(r);
resctrl_fs_teardown();
+ rdt_flush_limbo();
+
+ /*
+ * Clear the CDP state last. free_rmid() translates between CLOSID and
+ * PARTID using the CDP state; clearing it first would make the
+ * translation inconsistent for the RMIDs being torn down.
+ */
+ rdt_disable_ctx();
+
if (resctrl_arch_alloc_capable())
resctrl_arch_disable_alloc();
if (resctrl_arch_mon_capable())
--
2.34.1
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v10 RESEND 07/15] fs/resctrl: Add resctrl_arch_rmid_idx_match() helper
2026-10-20 13:42 [PATCH v10 RESEND 00/15] arm_mpam: Introduce Narrow-PARTID feature Jinjie Ruan
` (5 preceding siblings ...)
2026-10-20 13:42 ` [PATCH v10 RESEND 06/15] fs/resctrl: Unmount resctrl with the CDP state it was mounted with Jinjie Ruan
@ 2026-10-20 13:42 ` Jinjie Ruan
2026-10-20 13:42 ` [PATCH v10 RESEND 08/15] arm_mpam: resctrl: Add resctrl_closid_rmid_to_partid_pmg() helper Jinjie Ruan
` (7 subsequent siblings)
14 siblings, 0 replies; 22+ messages in thread
From: Jinjie Ruan @ 2026-10-20 13:42 UTC (permalink / raw)
To: tony.luck, reinette.chatre, Dave.Martin, james.morse, babu.moger,
tglx, mingo, bp, dave.hansen, hpa, ben.horgan, fenghuay,
ruanjinjie, tan.shaopeng, zengheng4, jic23, gshan,
catalin.marinas, x86, linux-kernel
The current RMID allocator assumes that RMID and CLOSID are entirely
independent or follow a rigid 2D encoding. This prevents architectures
where an RMID is bound to a specific CLOSID from properly filtering
free RMIDs.
Introduce resctrl_arch_rmid_idx_match() to allow architectures to define
their own compatibility rules between a target CLOSID and an allocated
RMID's CLOSID.
No functional change.
Cc: Reinette Chatre <reinette.chatre@intel.com>
Cc: Dave Martin <Dave.Martin@arm.com>
Cc: James Morse <james.morse@arm.com>
Cc: Babu Moger <babu.moger@amd.com>
Cc: Thomas Gleixner <tglx@kernel.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Borislav Petkov <bp@alien8.de>
Cc: Dave Hansen <dave.hansen@linux.intel.com>
Cc: H. Peter Anvin <hpa@zytor.com>
Cc: Ben Horgan <ben.horgan@arm.com>
Cc: Fenghua Yu <fenghuay@nvidia.com>
Suggested-by: James Morse <james.morse@arm.com>
Signed-off-by: Zeng Heng <zengheng4@huawei.com>
Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
---
arch/x86/include/asm/resctrl.h | 6 ++++++
drivers/resctrl/mpam_resctrl.c | 6 ++++++
fs/resctrl/monitor.c | 13 +++----------
include/linux/arm_mpam.h | 1 +
4 files changed, 16 insertions(+), 10 deletions(-)
diff --git a/arch/x86/include/asm/resctrl.h b/arch/x86/include/asm/resctrl.h
index 8f6edcdcfd87..7bb297a0ab6e 100644
--- a/arch/x86/include/asm/resctrl.h
+++ b/arch/x86/include/asm/resctrl.h
@@ -181,6 +181,12 @@ static inline u32 resctrl_arch_rmid_idx_encode(u32 ignored, u32 rmid)
return rmid;
}
+/* x86 RMIDs are independent; any RMID can be used with any CLOSID. */
+static inline bool resctrl_arch_rmid_idx_match(u32 closid, u32 rmid_closid)
+{
+ return true;
+}
+
/* x86 can always read an rmid, nothing needs allocating */
struct rdt_resource;
static inline void *resctrl_arch_mon_ctx_alloc(struct rdt_resource *r,
diff --git a/drivers/resctrl/mpam_resctrl.c b/drivers/resctrl/mpam_resctrl.c
index 23a8771ef148..ee15367e36eb 100644
--- a/drivers/resctrl/mpam_resctrl.c
+++ b/drivers/resctrl/mpam_resctrl.c
@@ -279,6 +279,12 @@ u32 resctrl_arch_rmid_idx_encode(u32 closid, u32 rmid)
return closid * (mpam_pmg_max + 1) + rmid;
}
+/* Check if the RMID's associated closid matches the target CLOSID. */
+bool resctrl_arch_rmid_idx_match(u32 closid, u32 rmid_closid)
+{
+ return closid == rmid_closid;
+}
+
void resctrl_arch_rmid_idx_decode(u32 idx, u32 *closid, u32 *rmid)
{
*closid = idx / (mpam_pmg_max + 1);
diff --git a/fs/resctrl/monitor.c b/fs/resctrl/monitor.c
index ffa9c67c9f00..b5a3b70bb809 100644
--- a/fs/resctrl/monitor.c
+++ b/fs/resctrl/monitor.c
@@ -208,23 +208,16 @@ bool has_busy_rmid(struct rdt_l3_mon_domain *d)
static struct rmid_entry *resctrl_find_free_rmid(u32 closid)
{
struct rmid_entry *itr;
- u32 itr_idx, cmp_idx;
if (list_empty(&rmid_free_lru))
return rmid_limbo_count ? ERR_PTR(-EBUSY) : ERR_PTR(-ENOSPC);
list_for_each_entry(itr, &rmid_free_lru, list) {
/*
- * Get the index of this free RMID, and the index it would need
- * to be if it were used with this CLOSID.
- * If the CLOSID is irrelevant on this architecture, the two
- * index values are always the same on every entry and thus the
- * very first entry will be returned.
+ * Check if the free RMID matches the target CLOSID.
+ * Architecture-specific rules apply.
*/
- itr_idx = resctrl_arch_rmid_idx_encode(itr->closid, itr->rmid);
- cmp_idx = resctrl_arch_rmid_idx_encode(closid, itr->rmid);
-
- if (itr_idx == cmp_idx)
+ if (resctrl_arch_rmid_idx_match(closid, itr->closid))
return itr;
}
diff --git a/include/linux/arm_mpam.h b/include/linux/arm_mpam.h
index f92a36187a52..0bc8049a3733 100644
--- a/include/linux/arm_mpam.h
+++ b/include/linux/arm_mpam.h
@@ -60,6 +60,7 @@ void resctrl_arch_sched_in(struct task_struct *tsk);
bool resctrl_arch_match_closid(struct task_struct *tsk, u32 closid);
bool resctrl_arch_match_rmid(struct task_struct *tsk, u32 closid, u32 rmid);
u32 resctrl_arch_rmid_idx_encode(u32 closid, u32 rmid);
+bool resctrl_arch_rmid_idx_match(u32 closid, u32 rmid_closid);
void resctrl_arch_rmid_idx_decode(u32 idx, u32 *closid, u32 *rmid);
u32 resctrl_arch_system_num_rmid_idx(void);
--
2.34.1
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v10 RESEND 08/15] arm_mpam: resctrl: Add resctrl_closid_rmid_to_partid_pmg() helper
2026-10-20 13:42 [PATCH v10 RESEND 00/15] arm_mpam: Introduce Narrow-PARTID feature Jinjie Ruan
` (6 preceding siblings ...)
2026-10-20 13:42 ` [PATCH v10 RESEND 07/15] fs/resctrl: Add resctrl_arch_rmid_idx_match() helper Jinjie Ruan
@ 2026-10-20 13:42 ` Jinjie Ruan
2026-10-20 13:42 ` [PATCH v10 RESEND 09/15] arm_mpam: Add a helper to check a PARTID is in a CLOSID Jinjie Ruan
` (6 subsequent siblings)
14 siblings, 0 replies; 22+ messages in thread
From: Jinjie Ruan @ 2026-10-20 13:42 UTC (permalink / raw)
To: tony.luck, reinette.chatre, Dave.Martin, james.morse, babu.moger,
tglx, mingo, bp, dave.hansen, hpa, ben.horgan, fenghuay,
ruanjinjie, tan.shaopeng, zengheng4, jic23, gshan,
catalin.marinas, x86, linux-kernel
The translation from resctrl (CLOSID, RMID) to MPAM (PARTID, PMG)
is currently open-coded across multiple paths (CPU default settings,
task setters, matching logic, and monitor readers). This duplicates
the special-case logic for CDP handling in multiple places.
Consolidate this translation into a single helper. This not only
eliminates duplication but also provides a unified checkpoint to support
non-linear partitioning mappings (such as PARTID narrowing) in subsequent
patches.
No functional change.
Cc: James Morse <james.morse@arm.com>
Cc: Ben Horgan <ben.horgan@arm.com>
Cc: Reinette Chatre <reinette.chatre@intel.com>
Cc: Fenghua Yu <fenghuay@nvidia.com>
Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
---
drivers/resctrl/mpam_resctrl.c | 64 ++++++++++++++++++++--------------
1 file changed, 38 insertions(+), 26 deletions(-)
diff --git a/drivers/resctrl/mpam_resctrl.c b/drivers/resctrl/mpam_resctrl.c
index ee15367e36eb..9bb1f1569601 100644
--- a/drivers/resctrl/mpam_resctrl.c
+++ b/drivers/resctrl/mpam_resctrl.c
@@ -298,25 +298,37 @@ void resctrl_arch_sched_in(struct task_struct *tsk)
mpam_thread_switch(tsk);
}
-void resctrl_arch_set_cpu_default_closid_rmid(int cpu, u32 closid, u32 rmid)
+static void resctrl_closid_rmid_to_partid_pmg(u32 closid, u32 rmid,
+ u32 *partid_d, u32 *partid_i,
+ u8 *pmg_d, u8 *pmg_i)
{
WARN_ON_ONCE(closid > U16_MAX);
WARN_ON_ONCE(rmid > U8_MAX);
if (!cdp_enabled) {
- mpam_set_cpu_defaults(cpu, closid, closid, rmid, rmid);
+ *partid_d = *partid_i = closid;
+ *pmg_d = *pmg_i = rmid;
} else {
/*
* When CDP is enabled, resctrl halves the closid range and we
* use odd/even partid for one closid.
*/
- u32 partid_d = resctrl_get_config_index(closid, CDP_DATA);
- u32 partid_i = resctrl_get_config_index(closid, CDP_CODE);
-
- mpam_set_cpu_defaults(cpu, partid_d, partid_i, rmid, rmid);
+ *partid_d = resctrl_get_config_index(closid, CDP_DATA);
+ *partid_i = resctrl_get_config_index(closid, CDP_CODE);
+ *pmg_d = *pmg_i = rmid;
}
}
+void resctrl_arch_set_cpu_default_closid_rmid(int cpu, u32 closid, u32 rmid)
+{
+ u32 partid_d, partid_i;
+ u8 pmg_d, pmg_i;
+
+ resctrl_closid_rmid_to_partid_pmg(closid, rmid, &partid_d, &partid_i,
+ &pmg_d, &pmg_i);
+ mpam_set_cpu_defaults(cpu, partid_d, partid_i, pmg_d, pmg_i);
+}
+
void resctrl_arch_sync_cpu_closid_rmid(void *info)
{
struct resctrl_cpu_defaults *r = info;
@@ -333,17 +345,12 @@ void resctrl_arch_sync_cpu_closid_rmid(void *info)
void resctrl_arch_set_closid_rmid(struct task_struct *tsk, u32 closid, u32 rmid)
{
- WARN_ON_ONCE(closid > U16_MAX);
- WARN_ON_ONCE(rmid > U8_MAX);
+ u32 partid_d, partid_i;
+ u8 pmg_d, pmg_i;
- if (!cdp_enabled) {
- mpam_set_task_partid_pmg(tsk, closid, closid, rmid, rmid);
- } else {
- u32 partid_d = resctrl_get_config_index(closid, CDP_DATA);
- u32 partid_i = resctrl_get_config_index(closid, CDP_CODE);
-
- mpam_set_task_partid_pmg(tsk, partid_d, partid_i, rmid, rmid);
- }
+ resctrl_closid_rmid_to_partid_pmg(closid, rmid, &partid_d, &partid_i,
+ &pmg_d, &pmg_i);
+ mpam_set_task_partid_pmg(tsk, partid_d, partid_i, pmg_d, pmg_i);
}
bool resctrl_arch_match_closid(struct task_struct *tsk, u32 closid)
@@ -361,13 +368,15 @@ bool resctrl_arch_match_closid(struct task_struct *tsk, u32 closid)
bool resctrl_arch_match_rmid(struct task_struct *tsk, u32 closid, u32 rmid)
{
u64 regval = mpam_get_regval(tsk);
- u32 tsk_closid = FIELD_GET(MPAM0_EL1_PARTID_D, regval);
- u32 tsk_rmid = FIELD_GET(MPAM0_EL1_PMG_D, regval);
+ u32 tsk_partid = FIELD_GET(MPAM0_EL1_PARTID_D, regval);
+ u32 tsk_pmg = FIELD_GET(MPAM0_EL1_PMG_D, regval);
+ u32 partid_d, partid_i;
+ u8 pmg_d, pmg_i;
- if (cdp_enabled)
- tsk_closid >>= 1;
+ resctrl_closid_rmid_to_partid_pmg(closid, rmid, &partid_d, &partid_i,
+ &pmg_d, &pmg_i);
- return (tsk_closid == closid) && (tsk_rmid == rmid);
+ return (tsk_partid == partid_d) && (tsk_pmg == pmg_d);
}
struct rdt_resource *resctrl_arch_get_resource(enum resctrl_res_level l)
@@ -461,16 +470,19 @@ static int __read_mon(struct mpam_resctrl_mon *mon, struct mpam_component *mon_c
int mon_idx,
enum resctrl_conf_type cdp_type, u32 closid, u32 rmid, u64 *val)
{
+ u32 partid, partid_d, partid_i;
struct mon_cfg cfg;
+ u8 pmg_d, pmg_i;
if (!mpam_is_enabled())
return -EINVAL;
- /* Shift closid to account for CDP */
- closid = resctrl_get_config_index(closid, cdp_type);
+ resctrl_closid_rmid_to_partid_pmg(closid, rmid, &partid_d, &partid_i,
+ &pmg_d, &pmg_i);
+ partid = (cdp_type == CDP_CODE) ? partid_i : partid_d;
if (mon_idx == USE_PRE_ALLOCATED) {
- int mbwu_idx = resctrl_arch_rmid_idx_encode(closid, rmid);
+ int mbwu_idx = resctrl_arch_rmid_idx_encode(partid, rmid);
mon_idx = mon->mbwu_idx_to_mon[mbwu_idx];
if (mon_idx == -1)
@@ -485,8 +497,8 @@ static int __read_mon(struct mpam_resctrl_mon *mon, struct mpam_component *mon_c
cfg = (struct mon_cfg) {
.mon = mon_idx,
.match_pmg = true,
- .partid = closid,
- .pmg = rmid,
+ .partid = partid,
+ .pmg = pmg_d,
};
return mpam_msmon_read(mon_comp, &cfg, mon_type, val);
--
2.34.1
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v10 RESEND 09/15] arm_mpam: Add a helper to check a PARTID is in a CLOSID
2026-10-20 13:42 [PATCH v10 RESEND 00/15] arm_mpam: Introduce Narrow-PARTID feature Jinjie Ruan
` (7 preceding siblings ...)
2026-10-20 13:42 ` [PATCH v10 RESEND 08/15] arm_mpam: resctrl: Add resctrl_closid_rmid_to_partid_pmg() helper Jinjie Ruan
@ 2026-10-20 13:42 ` Jinjie Ruan
2026-10-20 13:42 ` [PATCH v10 RESEND 10/15] arm_mpam: Track the PARTID and internal PARTID limits per class Jinjie Ruan
` (5 subsequent siblings)
14 siblings, 0 replies; 22+ messages in thread
From: Jinjie Ruan @ 2026-10-20 13:42 UTC (permalink / raw)
To: tony.luck, reinette.chatre, Dave.Martin, james.morse, babu.moger,
tglx, mingo, bp, dave.hansen, hpa, ben.horgan, fenghuay,
ruanjinjie, tan.shaopeng, zengheng4, jic23, gshan,
catalin.marinas, x86, linux-kernel
match_closid() open codes the CDP special case that turns the task
PARTID into the CLOSID it belongs to. Move it into
resctrl_partid_in_closid(), so the question "does this PARTID belong to
this control group" is answered in one place.
No functional change.
Cc: James Morse <james.morse@arm.com>
Cc: Ben Horgan <ben.horgan@arm.com>
Cc: Reinette Chatre <reinette.chatre@intel.com>
Cc: Fenghua Yu <fenghuay@nvidia.com>
Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
---
drivers/resctrl/mpam_resctrl.c | 16 +++++++++++-----
1 file changed, 11 insertions(+), 5 deletions(-)
diff --git a/drivers/resctrl/mpam_resctrl.c b/drivers/resctrl/mpam_resctrl.c
index 9bb1f1569601..bfff965177fb 100644
--- a/drivers/resctrl/mpam_resctrl.c
+++ b/drivers/resctrl/mpam_resctrl.c
@@ -353,15 +353,21 @@ void resctrl_arch_set_closid_rmid(struct task_struct *tsk, u32 closid, u32 rmid)
mpam_set_task_partid_pmg(tsk, partid_d, partid_i, pmg_d, pmg_i);
}
+/* Does the request PARTID belong to this control group? */
+static bool resctrl_partid_in_closid(u16 partid, u32 closid)
+{
+ if (cdp_enabled)
+ partid >>= 1;
+
+ return partid == closid;
+}
+
bool resctrl_arch_match_closid(struct task_struct *tsk, u32 closid)
{
u64 regval = mpam_get_regval(tsk);
- u32 tsk_closid = FIELD_GET(MPAM0_EL1_PARTID_D, regval);
-
- if (cdp_enabled)
- tsk_closid >>= 1;
+ u32 tsk_partid = FIELD_GET(MPAM0_EL1_PARTID_D, regval);
- return tsk_closid == closid;
+ return resctrl_partid_in_closid(tsk_partid, closid);
}
/* The task's pmg is not unique, the partid must be considered too */
--
2.34.1
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v10 RESEND 10/15] arm_mpam: Track the PARTID and internal PARTID limits per class
2026-10-20 13:42 [PATCH v10 RESEND 00/15] arm_mpam: Introduce Narrow-PARTID feature Jinjie Ruan
` (8 preceding siblings ...)
2026-10-20 13:42 ` [PATCH v10 RESEND 09/15] arm_mpam: Add a helper to check a PARTID is in a CLOSID Jinjie Ruan
@ 2026-10-20 13:42 ` Jinjie Ruan
2026-10-20 13:42 ` [PATCH v10 RESEND 11/15] arm_mpam: Divide the PARTIDs between the control groups Jinjie Ruan
` (4 subsequent siblings)
14 siblings, 0 replies; 22+ messages in thread
From: Jinjie Ruan @ 2026-10-20 13:42 UTC (permalink / raw)
To: tony.luck, reinette.chatre, Dave.Martin, james.morse, babu.moger,
tglx, mingo, bp, dave.hansen, hpa, ben.horgan, fenghuay,
ruanjinjie, tan.shaopeng, zengheng4, jic23, gshan,
catalin.marinas, x86, linux-kernel
The number of PARTIDs and PMGs a resource supports is global, and the
number of internal PARTIDs is not tracked at all. Tracking this per
class lets the resctrl glue code find the limits of the classes it will
use, and pick the number of control groups before it is initialised.
Resources that implement PARTID narrowing reduce msc->partid_max,
because they don't have one configuration per PARTID. Record the
internal PARTID limit alongside partid_max in struct mpam_props, and
take the smaller of the values a class's RIS advertise.
Cc: James Morse <james.morse@arm.com>
Cc: Ben Horgan <ben.horgan@arm.com>
Cc: Reinette Chatre <reinette.chatre@intel.com>
Cc: Fenghua Yu <fenghuay@nvidia.com>
Suggested-by: James Morse <james.morse@arm.com>
Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
---
drivers/resctrl/mpam_devices.c | 34 ++++++++++++++++++++++++---------
drivers/resctrl/mpam_internal.h | 2 ++
2 files changed, 27 insertions(+), 9 deletions(-)
diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
index a44d66a5733d..7f88a0170c05 100644
--- a/drivers/resctrl/mpam_devices.c
+++ b/drivers/resctrl/mpam_devices.c
@@ -793,7 +793,6 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris)
struct mpam_msc *msc = ris->vmsc->msc;
struct device *dev = &msc->pdev->dev;
struct mpam_props *props = &ris->props;
- struct mpam_class *class = ris->vmsc->comp->class;
lockdep_assert_held(&msc->probe_lock);
lockdep_assert_held(&msc->part_sel_lock);
@@ -938,18 +937,20 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris)
}
/*
- * RIS with PARTID narrowing don't have enough storage for one
- * configuration per PARTID. If these are in a class we could use,
- * reduce the supported partid_max to match the number of intpartid.
- * If the class is unknown, just ignore it.
+ * A RIS that implements PARTID narrowing has one configuration per
+ * internal PARTID, and several request PARTIDs share it. Record both
+ * limits in the resource properties.
*/
- if (FIELD_GET(MPAMF_IDR_HAS_PARTID_NRW, ris->idr) &&
- class->type != MPAM_CLASS_UNKNOWN) {
+ props->partid_max = msc->partid_max;
+ props->intpartid_max = msc->partid_max;
+
+ if (FIELD_GET(MPAMF_IDR_HAS_PARTID_NRW, ris->idr)) {
u32 nrwidr = mpam_read_partsel_reg(msc, PARTID_NRW_IDR);
- u16 partid_max = FIELD_GET(MPAMF_PARTID_NRW_IDR_INTPARTID_MAX, nrwidr);
+ u16 intpartid_max = FIELD_GET(MPAMF_PARTID_NRW_IDR_INTPARTID_MAX, nrwidr);
mpam_set_feature(mpam_feat_partid_nrw, props);
- msc->partid_max = min(msc->partid_max, partid_max);
+ props->intpartid_max = min(props->intpartid_max, intpartid_max);
+ msc->partid_max = min(msc->partid_max, intpartid_max);
}
}
@@ -2270,6 +2271,21 @@ static void __props_mismatch(struct mpam_props *parent,
child->num_mbwu_mon);
}
+ /*
+ * Take the smallest, so the result is safe for every RIS in the class.
+ * The first aliasing merge into a zero-initialized parent must copy
+ * the child's value, otherwise min() would clamp the limit to zero.
+ */
+ if (alias && !parent->partid_max)
+ parent->partid_max = child->partid_max;
+ else
+ parent->partid_max = min(parent->partid_max, child->partid_max);
+
+ if (alias && !parent->intpartid_max)
+ parent->intpartid_max = child->intpartid_max;
+ else
+ parent->intpartid_max = min(parent->intpartid_max, child->intpartid_max);
+
if (CAN_MERGE_FEAT(parent, child, mpam_feat_intpri_part, alias)) {
parent->intpri_wd = child->intpri_wd;
} else if (MISMATCHED_FEAT(parent, child, mpam_feat_intpri_part,
diff --git a/drivers/resctrl/mpam_internal.h b/drivers/resctrl/mpam_internal.h
index def0e3a65c23..0a2177241615 100644
--- a/drivers/resctrl/mpam_internal.h
+++ b/drivers/resctrl/mpam_internal.h
@@ -203,6 +203,8 @@ struct mpam_props {
u16 dspri_wd;
u16 num_csu_mon;
u16 num_mbwu_mon;
+ u16 partid_max;
+ u16 intpartid_max;
/*
* Kunit tests use memset() to set up feature combinations that should be
--
2.34.1
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v10 RESEND 11/15] arm_mpam: Divide the PARTIDs between the control groups
2026-10-20 13:42 [PATCH v10 RESEND 00/15] arm_mpam: Introduce Narrow-PARTID feature Jinjie Ruan
` (9 preceding siblings ...)
2026-10-20 13:42 ` [PATCH v10 RESEND 10/15] arm_mpam: Track the PARTID and internal PARTID limits per class Jinjie Ruan
@ 2026-10-20 13:42 ` Jinjie Ruan
2026-10-20 13:42 ` [PATCH v10 RESEND 12/15] arm_mpam: Map resctrl RMIDs onto the request PARTIDs Jinjie Ruan
` (3 subsequent siblings)
14 siblings, 0 replies; 22+ messages in thread
From: Jinjie Ruan @ 2026-10-20 13:42 UTC (permalink / raw)
To: tony.luck, reinette.chatre, Dave.Martin, james.morse, babu.moger,
tglx, mingo, bp, dave.hansen, hpa, ben.horgan, fenghuay,
ruanjinjie, tan.shaopeng, zengheng4, jic23, gshan,
catalin.marinas, x86, linux-kernel
To program a class that implements PARTID narrowing once per control
group, the driver needs to know which request PARTIDs belong to which
control group. Add a map from request PARTID to control group, and
program a group's configuration into every request PARTID that maps onto
it.
The map is empty until the resctrl glue code sets one up, so every
PARTID is its own control group and the behaviour is unchanged. A class
that implements narrowing writes the request-PARTID to internal-PARTID
mapping, so its configuration is only programmed for the request PARTID
that maps onto itself.
Cc: James Morse <james.morse@arm.com>
Cc: Ben Horgan <ben.horgan@arm.com>
Cc: Reinette Chatre <reinette.chatre@intel.com>
Cc: Fenghua Yu <fenghuay@nvidia.com>
Signed-off-by: Zeng Heng <zengheng4@huawei.com>
Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
---
drivers/resctrl/mpam_devices.c | 74 +++++++++++++++++++++++++++++----
drivers/resctrl/mpam_internal.h | 2 +
2 files changed, 68 insertions(+), 8 deletions(-)
diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
index 7f88a0170c05..f81a2df11428 100644
--- a/drivers/resctrl/mpam_devices.c
+++ b/drivers/resctrl/mpam_devices.c
@@ -67,6 +67,25 @@ u8 mpam_pmg_max;
static bool partid_max_init, partid_max_published;
static DEFINE_SPINLOCK(partid_max_lock);
+/*
+ * Configuration constraints for resctrl-to-MPAM partitioning.
+ * mpam_num_closid == 0 indicates resctrl is unmounted or inactive.
+ */
+static u32 mpam_num_closid;
+static u32 mpam_num_partid;
+
+/*
+ * Map a request PARTID onto its owning control group using modulo
+ * interleaving. Without resctrl active, every PARTID maps 1:1 onto itself.
+ */
+u16 mpam_partid_to_intpartid(u16 partid)
+{
+ if (!mpam_num_closid || partid >= mpam_num_partid)
+ return partid;
+
+ return partid % mpam_num_closid;
+}
+
/*
* mpam is enabled once all devices have been probed from CPU online callbacks,
* scheduled via this work_struct. If access to an MSC depends on a CPU that
@@ -1560,14 +1579,24 @@ static void mpam_reprogram_ris_partid(struct mpam_msc_ris *ris, u16 partid,
if (mpam_has_feature(mpam_feat_partid_nrw, rprops)) {
/* Update the intpartid mapping */
+ u16 intpartid = mpam_partid_to_intpartid(partid);
+
mpam_write_partsel_reg(msc, INTPARTID,
- MPAMCFG_INTPARTID_INTERNAL | partid);
+ MPAMCFG_INTPARTID_INTERNAL | intpartid);
+
+ /*
+ * The internal PARTID holds the configuration. Only the
+ * request PARTID that maps onto itself writes the controls,
+ * the others share the same internal PARTID.
+ */
+ if (partid != intpartid)
+ goto post_config_change;
/*
* Then switch to the 'internal' partid to update the
* configuration.
*/
- __mpam_intpart_sel(ris->ris_idx, partid, msc);
+ __mpam_intpart_sel(ris->ris_idx, intpartid, msc);
}
if (mpam_has_feature(mpam_feat_cpor_part, rprops)) {
@@ -1641,6 +1670,7 @@ static void mpam_reprogram_ris_partid(struct mpam_msc_ris *ris, u16 partid,
mpam_write_partsel_reg(msc, PRI, pri_val);
}
+post_config_change:
mpam_quirk_post_config_change(ris, partid, cfg);
mutex_unlock(&msc->part_sel_lock);
@@ -1771,19 +1801,41 @@ struct mpam_write_config_arg {
static int __write_config(void *arg)
{
struct mpam_write_config_arg *c = arg;
+ u32 partid;
+
+ if (!mpam_num_closid) {
+ mpam_reprogram_ris_partid(c->ris, c->partid,
+ &c->comp->cfg[c->partid]);
+ return 0;
+ }
+
+ /* c->partid is the control group, and must be a valid one. */
+ WARN_ON_ONCE(c->partid >= mpam_num_closid);
+
+ /*
+ * A control group owns every request PARTID that maps onto it. Write
+ * the group's configuration into each of them. Resources that
+ * implement narrowing collapse them back onto a single internal
+ * PARTID, so only the first write changes the hardware.
+ */
+ for (partid = 0; partid < mpam_num_partid; partid++) {
+ if (mpam_partid_to_intpartid(partid) != c->partid)
+ continue;
- mpam_reprogram_ris_partid(c->ris, c->partid, &c->comp->cfg[c->partid]);
+ mpam_reprogram_ris_partid(c->ris, partid,
+ &c->comp->cfg[c->partid]);
+ }
return 0;
}
static void mpam_reprogram_msc(struct mpam_msc *msc)
{
- u32 partid;
- bool reset;
- struct mpam_config *cfg;
- struct mpam_msc_ris *ris;
struct mpam_write_config_arg arg;
+ struct mpam_msc_ris *ris;
+ struct mpam_config *cfg;
+ u32 partid, loop_max;
+ bool reset;
/*
* No lock for mpam_partid_max as partid_max_published has been
@@ -1803,7 +1855,13 @@ static void mpam_reprogram_msc(struct mpam_msc *msc)
arg.comp = ris->vmsc->comp;
arg.ris = ris;
reset = true;
- for (partid = 0; partid <= mpam_partid_max; partid++) {
+ /*
+ * Configurations are stored per control group. When resctrl
+ * has grouped several PARTIDs together, __write_config()
+ * expands one group into all of its PARTIDs.
+ */
+ loop_max = mpam_num_closid ? mpam_num_closid : mpam_partid_max + 1;
+ for (partid = 0; partid < loop_max; partid++) {
cfg = &ris->vmsc->comp->cfg[partid];
if (!bitmap_empty(cfg->features, MPAM_FEATURE_LAST))
reset = false;
diff --git a/drivers/resctrl/mpam_internal.h b/drivers/resctrl/mpam_internal.h
index 0a2177241615..5a7590568a05 100644
--- a/drivers/resctrl/mpam_internal.h
+++ b/drivers/resctrl/mpam_internal.h
@@ -458,6 +458,8 @@ extern struct list_head mpam_classes;
extern u16 mpam_partid_max;
extern u8 mpam_pmg_max;
+u16 mpam_partid_to_intpartid(u16 partid);
+
/* Scheduled work callback to enable mpam once all MSC have been probed */
void mpam_enable(struct work_struct *work);
void mpam_disable(struct work_struct *work);
--
2.34.1
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v10 RESEND 12/15] arm_mpam: Map resctrl RMIDs onto the request PARTIDs
2026-10-20 13:42 [PATCH v10 RESEND 00/15] arm_mpam: Introduce Narrow-PARTID feature Jinjie Ruan
` (10 preceding siblings ...)
2026-10-20 13:42 ` [PATCH v10 RESEND 11/15] arm_mpam: Divide the PARTIDs between the control groups Jinjie Ruan
@ 2026-10-20 13:42 ` Jinjie Ruan
2026-10-20 13:42 ` [PATCH v10 RESEND 13/15] arm_mpam: Identify the non-aliasing controls Jinjie Ruan
` (2 subsequent siblings)
14 siblings, 0 replies; 22+ messages in thread
From: Jinjie Ruan @ 2026-10-20 13:42 UTC (permalink / raw)
To: tony.luck, reinette.chatre, Dave.Martin, james.morse, babu.moger,
tglx, mingo, bp, dave.hansen, hpa, ben.horgan, fenghuay,
ruanjinjie, tan.shaopeng, zengheng4, jic23, gshan,
catalin.marinas, x86, linux-kernel
From: Zeng Heng <zengheng4@huawei.com>
The current resctrl RMID allocation assumes a rigid 2D encoding, which
limits the total number of monitoring groups to the hardware CLOSID count.
To decouple control and monitoring, redefine resctrl_arch_rmid_idx_encode()
as a flat, global monitoring index. When extra request PARTIDs are active,
the task's hardware PARTID and PMG are derived directly from this global
RMID via modulo division. Otherwise, legacy 1:1 behavior is preserved.
CDP and PARTID narrowing can now coexist through a two-phase mapping:
when both features are active, extract reqPARTID from rmid first, then
apply CDP's even/odd split on that reqPARTID.
No functional change for now as resctrl_using_extra_partids
remains false.
Cc: James Morse <james.morse@arm.com>
Cc: Ben Horgan <ben.horgan@arm.com>
Cc: Reinette Chatre <reinette.chatre@intel.com>
Cc: Fenghua Yu <fenghuay@nvidia.com>
Suggested-by: James Morse <james.morse@arm.com>
Link: https://lore.kernel.org/all/2944b506-a462-42f8-95cf-404241fb27f0@arm.com/
Signed-off-by: Zeng Heng <zengheng4@huawei.com>
Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
---
drivers/resctrl/mpam_resctrl.c | 76 ++++++++++++++++++++++++++++------
1 file changed, 64 insertions(+), 12 deletions(-)
diff --git a/drivers/resctrl/mpam_resctrl.c b/drivers/resctrl/mpam_resctrl.c
index bfff965177fb..99b43ff2008f 100644
--- a/drivers/resctrl/mpam_resctrl.c
+++ b/drivers/resctrl/mpam_resctrl.c
@@ -62,6 +62,9 @@ static DEFINE_MUTEX(domain_list_lock);
*/
static bool cdp_enabled;
+/* If resctrl is leveraging extra request PARTIDs for monitoring. */
+static bool resctrl_using_extra_partids;
+
/*
* We use cacheinfo to discover the size of the caches and their id. cacheinfo
* populates this from a device_initcall(). mpam_resctrl_setup() must wait.
@@ -274,8 +277,28 @@ u32 resctrl_arch_system_num_rmid_idx(void)
return (mpam_pmg_max + 1) * (mpam_partid_max + 1);
}
+/* The request PARTID and PMG a resctrl RMID maps onto. */
+static u16 resctrl_rmid_to_partid(u32 rmid)
+{
+ return rmid / (mpam_pmg_max + 1);
+}
+
+static u8 resctrl_rmid_to_pmg(u32 rmid)
+{
+ return rmid % (mpam_pmg_max + 1);
+}
+
+/* The control group that owns a request PARTID. */
+static u16 resctrl_partid_to_closid(u16 partid)
+{
+ return mpam_partid_to_intpartid(partid);
+}
+
u32 resctrl_arch_rmid_idx_encode(u32 closid, u32 rmid)
{
+ if (resctrl_using_extra_partids)
+ return rmid;
+
return closid * (mpam_pmg_max + 1) + rmid;
}
@@ -287,8 +310,18 @@ bool resctrl_arch_rmid_idx_match(u32 closid, u32 rmid_closid)
void resctrl_arch_rmid_idx_decode(u32 idx, u32 *closid, u32 *rmid)
{
- *closid = idx / (mpam_pmg_max + 1);
- *rmid = idx % (mpam_pmg_max + 1);
+ if (resctrl_using_extra_partids) {
+ if (rmid)
+ *rmid = idx;
+ if (closid)
+ *closid = resctrl_partid_to_closid(resctrl_rmid_to_partid(idx));
+ return;
+ }
+
+ if (closid)
+ *closid = idx / (mpam_pmg_max + 1);
+ if (rmid)
+ *rmid = idx % (mpam_pmg_max + 1);
}
void resctrl_arch_sched_in(struct task_struct *tsk)
@@ -303,19 +336,35 @@ static void resctrl_closid_rmid_to_partid_pmg(u32 closid, u32 rmid,
u8 *pmg_d, u8 *pmg_i)
{
WARN_ON_ONCE(closid > U16_MAX);
- WARN_ON_ONCE(rmid > U8_MAX);
+ WARN_ON_ONCE(!resctrl_using_extra_partids && rmid > U8_MAX);
if (!cdp_enabled) {
- *partid_d = *partid_i = closid;
- *pmg_d = *pmg_i = rmid;
+ u16 partid;
+ u8 pmg;
+
+ if (resctrl_using_extra_partids) {
+ partid = resctrl_rmid_to_partid(rmid);
+ pmg = resctrl_rmid_to_pmg(rmid);
+ } else {
+ partid = closid;
+ pmg = rmid;
+ }
+
+ *partid_d = *partid_i = partid;
+ *pmg_d = *pmg_i = pmg;
} else {
- /*
- * When CDP is enabled, resctrl halves the closid range and we
- * use odd/even partid for one closid.
- */
- *partid_d = resctrl_get_config_index(closid, CDP_DATA);
- *partid_i = resctrl_get_config_index(closid, CDP_CODE);
- *pmg_d = *pmg_i = rmid;
+ if (resctrl_using_extra_partids) {
+ u16 req_partid = resctrl_rmid_to_partid(rmid);
+ u8 pmg = resctrl_rmid_to_pmg(rmid);
+
+ *partid_d = resctrl_get_config_index(req_partid, CDP_DATA);
+ *partid_i = resctrl_get_config_index(req_partid, CDP_CODE);
+ *pmg_d = *pmg_i = pmg;
+ } else {
+ *partid_d = resctrl_get_config_index(closid, CDP_DATA);
+ *partid_i = resctrl_get_config_index(closid, CDP_CODE);
+ *pmg_d = *pmg_i = rmid;
+ }
}
}
@@ -359,6 +408,9 @@ static bool resctrl_partid_in_closid(u16 partid, u32 closid)
if (cdp_enabled)
partid >>= 1;
+ if (resctrl_using_extra_partids)
+ partid = resctrl_partid_to_closid(partid);
+
return partid == closid;
}
--
2.34.1
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v10 RESEND 13/15] arm_mpam: Identify the non-aliasing controls
2026-10-20 13:42 [PATCH v10 RESEND 00/15] arm_mpam: Introduce Narrow-PARTID feature Jinjie Ruan
` (11 preceding siblings ...)
2026-10-20 13:42 ` [PATCH v10 RESEND 12/15] arm_mpam: Map resctrl RMIDs onto the request PARTIDs Jinjie Ruan
@ 2026-10-20 13:42 ` Jinjie Ruan
2026-10-20 13:42 ` [PATCH v10 RESEND 14/15] arm_mpam: Use the request PARTIDs beyond the internal range Jinjie Ruan
2026-10-20 13:42 ` [PATCH v10 RESEND 15/15] arm_mpam: resctrl: Add kunit tests for narrow PARTID helpers Jinjie Ruan
14 siblings, 0 replies; 22+ messages in thread
From: Jinjie Ruan @ 2026-10-20 13:42 UTC (permalink / raw)
To: tony.luck, reinette.chatre, Dave.Martin, james.morse, babu.moger,
tglx, mingo, bp, dave.hansen, hpa, ben.horgan, fenghuay,
ruanjinjie, tan.shaopeng, zengheng4, jic23, gshan,
catalin.marinas, x86, linux-kernel
A non-aliasing control can't be presented by writing the same value to
several PARTIDs, because its value is additive. This is used later to
decide whether a class's non-aliasing controls must be dropped before
its PARTIDs can be used as separate monitoring groups.
A control that aliases shares the resource, so writing the same value to
each PARTID keeps its meaning. Several PARTIDs that map onto one
internal PARTID are configured as one, so a class that implements
narrowing does not have this problem.
Cc: James Morse <james.morse@arm.com>
Cc: Ben Horgan <ben.horgan@arm.com>
Cc: Reinette Chatre <reinette.chatre@intel.com>
Cc: Fenghua Yu <fenghuay@nvidia.com>
Suggested-by: James Morse <james.morse@arm.com>
Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
---
drivers/resctrl/mpam_internal.h | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/drivers/resctrl/mpam_internal.h b/drivers/resctrl/mpam_internal.h
index 5a7590568a05..a4f2fdf7ac95 100644
--- a/drivers/resctrl/mpam_internal.h
+++ b/drivers/resctrl/mpam_internal.h
@@ -221,6 +221,25 @@ struct mpam_props {
#define mpam_set_feature(_feat, x) __set_bit(_feat, (x)->features)
#define mpam_clear_feature(_feat, x) __clear_bit(_feat, (x)->features)
+/*
+ * A non-aliasing control (e.g. a cache capacity) can't be presented by
+ * writing the same value to several PARTIDs: its value is additive, two 50%
+ * values add up to 100%. An aliasing control (e.g. a cache portion bitmap)
+ * shares the resource, so the same value written to each PARTID keeps its
+ * meaning.
+ */
+static inline bool
+mpam_props_has_non_aliasing_controls(const struct mpam_props *props)
+{
+ return mpam_has_feature(mpam_feat_mbw_max, props) ||
+ mpam_has_feature(mpam_feat_mbw_min, props) ||
+ mpam_has_feature(mpam_feat_mbw_prop, props) ||
+ mpam_has_feature(mpam_feat_cmax_cmax, props) ||
+ mpam_has_feature(mpam_feat_cmax_cmin, props) ||
+ mpam_has_feature(mpam_feat_cmax_cassoc, props) ||
+ mpam_has_feature(mpam_feat_cmax_softlim, props);
+}
+
/* Workaround bits for msc->quirks */
enum mpam_device_quirks {
T241_SCRUB_SHADOW_REGS,
--
2.34.1
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v10 RESEND 14/15] arm_mpam: Use the request PARTIDs beyond the internal range
2026-10-20 13:42 [PATCH v10 RESEND 00/15] arm_mpam: Introduce Narrow-PARTID feature Jinjie Ruan
` (12 preceding siblings ...)
2026-10-20 13:42 ` [PATCH v10 RESEND 13/15] arm_mpam: Identify the non-aliasing controls Jinjie Ruan
@ 2026-10-20 13:42 ` Jinjie Ruan
2026-10-20 13:42 ` [PATCH v10 RESEND 15/15] arm_mpam: resctrl: Add kunit tests for narrow PARTID helpers Jinjie Ruan
14 siblings, 0 replies; 22+ messages in thread
From: Jinjie Ruan @ 2026-10-20 13:42 UTC (permalink / raw)
To: tony.luck, reinette.chatre, Dave.Martin, james.morse, babu.moger,
tglx, mingo, bp, dave.hansen, hpa, ben.horgan, fenghuay,
ruanjinjie, tan.shaopeng, zengheng4, jic23, gshan,
catalin.marinas, x86, linux-kernel
A class that implements PARTID narrowing maps several request PARTIDs
onto one internal PARTID. The internal PARTIDs hold its controls, and
the request PARTIDs can each be a monitoring group. This gives one
control group more monitoring groups than the PMG count alone allows.
The extra request PARTIDs can only be used when no class would lose a
non-aliasing control: its value can not be written to every PARTID of a
control group, whereas an aliasing control's value can. Every class is
considered, including the ones resctrl does not use, so that an unknown
class can still gain a resctrl schema later. This is decided once,
before resctrl_init(), and there is no user interface for it.
The driver stops reducing msc->partid_max, so the full request PARTID
range is available, and the resctrl glue code starts handing out the
extra ones. A control group's configuration is written to every request
PARTID that maps onto it.
Cc: James Morse <james.morse@arm.com>
Cc: Ben Horgan <ben.horgan@arm.com>
Cc: Reinette Chatre <reinette.chatre@intel.com>
Cc: Fenghua Yu <fenghuay@nvidia.com>
Suggested-by: James Morse <james.morse@arm.com>
Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
---
drivers/resctrl/mpam_devices.c | 25 +++++++++++-
drivers/resctrl/mpam_internal.h | 2 +
drivers/resctrl/mpam_resctrl.c | 72 ++++++++++++++++++++++++++++++++-
3 files changed, 96 insertions(+), 3 deletions(-)
diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
index f81a2df11428..79bc3339a762 100644
--- a/drivers/resctrl/mpam_devices.c
+++ b/drivers/resctrl/mpam_devices.c
@@ -74,6 +74,18 @@ static DEFINE_SPINLOCK(partid_max_lock);
static u32 mpam_num_closid;
static u32 mpam_num_partid;
+void mpam_partid_map_init(u32 num_partid, u32 num_closid)
+{
+ mpam_num_partid = num_partid;
+ mpam_num_closid = num_closid;
+}
+
+void mpam_partid_map_exit(void)
+{
+ mpam_num_closid = 0;
+ mpam_num_partid = 0;
+}
+
/*
* Map a request PARTID onto its owning control group using modulo
* interleaving. Without resctrl active, every PARTID maps 1:1 onto itself.
@@ -959,6 +971,9 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris)
* A RIS that implements PARTID narrowing has one configuration per
* internal PARTID, and several request PARTIDs share it. Record both
* limits in the resource properties.
+ * Keep msc->partid_max at the full request PARTID range. The resctrl
+ * glue code will broadcast a control group's configuration to every
+ * request PARTID that maps onto that group's internal PARTID.
*/
props->partid_max = msc->partid_max;
props->intpartid_max = msc->partid_max;
@@ -969,7 +984,6 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris)
mpam_set_feature(mpam_feat_partid_nrw, props);
props->intpartid_max = min(props->intpartid_max, intpartid_max);
- msc->partid_max = min(msc->partid_max, intpartid_max);
}
}
@@ -1581,6 +1595,15 @@ static void mpam_reprogram_ris_partid(struct mpam_msc_ris *ris, u16 partid,
/* Update the intpartid mapping */
u16 intpartid = mpam_partid_to_intpartid(partid);
+ /*
+ * Without resctrl managing the mapping, the fallback maps
+ * every PARTID 1:1, which can exceed the internal PARTID
+ * range. Wrap onto an internal PARTID, like the narrowing
+ * mapping resctrl programs.
+ */
+ if (intpartid > rprops->intpartid_max)
+ intpartid %= (u32)rprops->intpartid_max + 1;
+
mpam_write_partsel_reg(msc, INTPARTID,
MPAMCFG_INTPARTID_INTERNAL | intpartid);
diff --git a/drivers/resctrl/mpam_internal.h b/drivers/resctrl/mpam_internal.h
index a4f2fdf7ac95..b62a0ecdd69f 100644
--- a/drivers/resctrl/mpam_internal.h
+++ b/drivers/resctrl/mpam_internal.h
@@ -478,6 +478,8 @@ extern u16 mpam_partid_max;
extern u8 mpam_pmg_max;
u16 mpam_partid_to_intpartid(u16 partid);
+void mpam_partid_map_init(u32 num_partid, u32 num_closid);
+void mpam_partid_map_exit(void);
/* Scheduled work callback to enable mpam once all MSC have been probed */
void mpam_enable(struct work_struct *work);
diff --git a/drivers/resctrl/mpam_resctrl.c b/drivers/resctrl/mpam_resctrl.c
index 99b43ff2008f..04282f56b6d2 100644
--- a/drivers/resctrl/mpam_resctrl.c
+++ b/drivers/resctrl/mpam_resctrl.c
@@ -62,6 +62,9 @@ static DEFINE_MUTEX(domain_list_lock);
*/
static bool cdp_enabled;
+static u32 resctrl_num_closid = 1;
+static u32 resctrl_num_mon_partid = 1;
+
/* If resctrl is leveraging extra request PARTIDs for monitoring. */
static bool resctrl_using_extra_partids;
@@ -269,12 +272,12 @@ static bool mpam_resctrl_hide_cdp(enum resctrl_res_level rid)
*/
u32 resctrl_arch_get_num_closid(struct rdt_resource *ignored)
{
- return mpam_partid_max + 1;
+ return resctrl_num_closid;
}
u32 resctrl_arch_system_num_rmid_idx(void)
{
- return (mpam_pmg_max + 1) * (mpam_partid_max + 1);
+ return (mpam_pmg_max + 1) * resctrl_num_mon_partid;
}
/* The request PARTID and PMG a resctrl RMID maps onto. */
@@ -2011,6 +2014,65 @@ void mpam_resctrl_offline_cpu(unsigned int cpu)
}
}
+/*
+ * Decide how the PARTID space is presented to resctrl. This runs once the
+ * classes are known, and before resctrl_init() advertises any limits to
+ * user-space.
+ *
+ * The extra request PARTIDs on a class that implements narrowing can be used
+ * as monitoring groups. That is only safe when no class would lose a
+ * non-aliasing control: its value can not be written to each PARTID of a
+ * control group, whereas an aliasing control's value can. A class that
+ * implements narrowing doesn't have the problem, as its PARTIDs are already
+ * configured as one.
+ *
+ * Every class is considered, including the ones resctrl doesn't use. An
+ * unknown class may gain a resctrl schema later, and using the extra PARTIDs
+ * would prevent that.
+ */
+static void mpam_resctrl_configure_partids(void)
+{
+ u32 num_closid = (u32)mpam_partid_max + 1;
+ bool can_expand = false, blocked = false;
+ struct mpam_class *class;
+
+ guard(srcu)(&mpam_srcu);
+ list_for_each_entry_srcu(class, &mpam_classes, classes_list,
+ srcu_read_lock_held(&mpam_srcu)) {
+ struct mpam_props *cprops = &class->props;
+
+ num_closid = min(num_closid, (u32)cprops->intpartid_max + 1);
+
+ if (mpam_has_feature(mpam_feat_partid_nrw, cprops)) {
+ if (cprops->intpartid_max < cprops->partid_max)
+ can_expand = true;
+ } else if (mpam_props_has_non_aliasing_controls(cprops)) {
+ pr_debug("class %u has non-aliasing controls without narrowing\n",
+ class->level);
+ blocked = true;
+ }
+ }
+
+ if (!num_closid)
+ num_closid = 1;
+
+ resctrl_num_closid = num_closid;
+ if (can_expand && !blocked) {
+ resctrl_using_extra_partids = true;
+ resctrl_num_mon_partid = (u32)mpam_partid_max + 1;
+ } else {
+ resctrl_using_extra_partids = false;
+ resctrl_num_mon_partid = num_closid;
+ }
+
+ /* Tell the driver how the request PARTIDs map onto control groups. */
+ mpam_partid_map_init(resctrl_num_mon_partid, resctrl_num_closid);
+
+ pr_debug("PARTIDs: monitor=%u closid=%u extra=%u\n",
+ resctrl_num_mon_partid, resctrl_num_closid,
+ resctrl_using_extra_partids);
+}
+
int mpam_resctrl_setup(void)
{
int err = 0;
@@ -2032,6 +2094,9 @@ int mpam_resctrl_setup(void)
mpam_resctrl_pick_caches();
mpam_resctrl_pick_mba();
+ /* Decide how the request PARTIDs map onto the control groups */
+ mpam_resctrl_configure_partids();
+
/* Initialise the resctrl structures from the classes */
for_each_mpam_resctrl_control(res, rid) {
if (!res->class)
@@ -2087,6 +2152,9 @@ void mpam_resctrl_exit(void)
WRITE_ONCE(resctrl_enabled, false);
resctrl_exit();
+
+ /* resctrl is gone: forget how the PARTIDs were grouped. */
+ mpam_partid_map_exit();
}
static void mpam_resctrl_teardown_mon(struct mpam_resctrl_mon *mon, struct mpam_class *class)
--
2.34.1
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v10 RESEND 15/15] arm_mpam: resctrl: Add kunit tests for narrow PARTID helpers
2026-10-20 13:42 [PATCH v10 RESEND 00/15] arm_mpam: Introduce Narrow-PARTID feature Jinjie Ruan
` (13 preceding siblings ...)
2026-10-20 13:42 ` [PATCH v10 RESEND 14/15] arm_mpam: Use the request PARTIDs beyond the internal range Jinjie Ruan
@ 2026-10-20 13:42 ` Jinjie Ruan
14 siblings, 0 replies; 22+ messages in thread
From: Jinjie Ruan @ 2026-10-20 13:42 UTC (permalink / raw)
To: tony.luck, reinette.chatre, Dave.Martin, james.morse, babu.moger,
tglx, mingo, bp, dave.hansen, hpa, ben.horgan, fenghuay,
ruanjinjie, tan.shaopeng, zengheng4, jic23, gshan,
catalin.marinas, x86, linux-kernel
Cover the RMID to request PARTID/PMG encoding, the request PARTID to
intPARTID modulo mapping, CLOSID membership checks across legacy, CDP,
narrowing and CDP+narrowing modes, and the (CLOSID, RMID) translation
helper.
The tests poke the partid map through mpam_partid_map_init() so they
do not depend on resctrl having been set up.
Enabled with CONFIG_MPAM_KUNIT_TEST; run with:
kunit.py run --arch=arm64 --kunitconfig=drivers/resctrl
Assisted-by: GLM:5.3
Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
---
drivers/resctrl/.kunitconfig | 4 +
drivers/resctrl/test_mpam_resctrl.c | 466 ++++++++++++++++++++++++++++
2 files changed, 470 insertions(+)
create mode 100644 drivers/resctrl/.kunitconfig
diff --git a/drivers/resctrl/.kunitconfig b/drivers/resctrl/.kunitconfig
new file mode 100644
index 000000000000..0cc78c0f7474
--- /dev/null
+++ b/drivers/resctrl/.kunitconfig
@@ -0,0 +1,4 @@
+CONFIG_KUNIT=y
+CONFIG_ARM64_MPAM=y
+CONFIG_RESCTRL_FS=y
+CONFIG_MPAM_KUNIT_TEST=y
diff --git a/drivers/resctrl/test_mpam_resctrl.c b/drivers/resctrl/test_mpam_resctrl.c
index b93d6ad87e43..f6620ea6f873 100644
--- a/drivers/resctrl/test_mpam_resctrl.c
+++ b/drivers/resctrl/test_mpam_resctrl.c
@@ -296,6 +296,462 @@ static void test_percent_to_max_rounding(struct kunit *test)
KUNIT_EXPECT_LE(test, 4 * num_rounded_up, 3 * total);
}
+/*
+ * Test cases for CDP and PARTID narrowing compatibility
+ */
+
+struct cdp_narrowing_test_case {
+ bool cdp_enabled;
+ bool narrowing_enabled;
+ u32 closid;
+ u32 rmid;
+ u8 pmg_max;
+ u32 expected_partid_d;
+ u32 expected_partid_i;
+ u8 expected_pmg_d;
+ u8 expected_pmg_i;
+ const char *desc;
+};
+
+static const struct cdp_narrowing_test_case cdp_narrowing_cases[] = {
+ {
+ .cdp_enabled = false,
+ .narrowing_enabled = false,
+ .closid = 5,
+ .rmid = 3,
+ .pmg_max = 127,
+ .expected_partid_d = 5,
+ .expected_partid_i = 5,
+ .expected_pmg_d = 3,
+ .expected_pmg_i = 3,
+ .desc = "CDP=0, narrow=0: basic mapping"
+ },
+ {
+ .cdp_enabled = false,
+ .narrowing_enabled = true,
+ .closid = 5,
+ .rmid = 258,
+ .pmg_max = 127,
+ .expected_partid_d = 2,
+ .expected_partid_i = 2,
+ .expected_pmg_d = 2,
+ .expected_pmg_i = 2,
+ .desc = "CDP=0, narrow=1: extract reqPARTID from rmid"
+ },
+ {
+ .cdp_enabled = true,
+ .narrowing_enabled = false,
+ .closid = 5,
+ .rmid = 3,
+ .pmg_max = 127,
+ .expected_partid_d = 10,
+ .expected_partid_i = 11,
+ .expected_pmg_d = 3,
+ .expected_pmg_i = 3,
+ .desc = "CDP=1, narrow=0: CDP DATA/CODE split"
+ },
+ {
+ .cdp_enabled = true,
+ .narrowing_enabled = true,
+ .closid = 5,
+ .rmid = 258,
+ .pmg_max = 127,
+ .expected_partid_d = 4,
+ .expected_partid_i = 5,
+ .expected_pmg_d = 2,
+ .expected_pmg_i = 2,
+ .desc = "CDP=1, narrow=1: both features combined"
+ },
+ {
+ .cdp_enabled = false,
+ .narrowing_enabled = false,
+ .closid = 65535,
+ .rmid = 0,
+ .pmg_max = 127,
+ .expected_partid_d = 65535,
+ .expected_partid_i = 65535,
+ .expected_pmg_d = 0,
+ .expected_pmg_i = 0,
+ .desc = "Boundary: max closid"
+ },
+ {
+ .cdp_enabled = false,
+ .narrowing_enabled = false,
+ .closid = 0,
+ .rmid = 127,
+ .pmg_max = 127,
+ .expected_partid_d = 0,
+ .expected_partid_i = 0,
+ .expected_pmg_d = 127,
+ .expected_pmg_i = 127,
+ .desc = "Boundary: max pmg"
+ },
+ {
+ .cdp_enabled = false,
+ .narrowing_enabled = true,
+ .closid = 0,
+ .rmid = 639,
+ .pmg_max = 159,
+ .expected_partid_d = 3,
+ .expected_partid_i = 3,
+ .expected_pmg_d = 159,
+ .expected_pmg_i = 159,
+ .desc = "Narrowing with pmg_max=159"
+ },
+ {
+ .cdp_enabled = true,
+ .narrowing_enabled = true,
+ .closid = 10,
+ .rmid = 1280,
+ .pmg_max = 127,
+ .expected_partid_d = 20,
+ .expected_partid_i = 21,
+ .expected_pmg_d = 0,
+ .expected_pmg_i = 0,
+ .desc = "CDP + narrowing with reqPARTID=10"
+ },
+};
+
+static void cdp_narrowing_test_desc(const struct cdp_narrowing_test_case *param,
+ char *desc)
+{
+ snprintf(desc, KUNIT_PARAM_DESC_SIZE, "%s", param->desc);
+}
+
+KUNIT_ARRAY_PARAM(cdp_narrowing, cdp_narrowing_cases, cdp_narrowing_test_desc);
+
+static void test_cdp_narrowing_mapping(struct kunit *test)
+{
+ const struct cdp_narrowing_test_case *param = test->param_value;
+ bool saved_cdp, saved_narrowing;
+ u32 partid_d, partid_i;
+ u8 saved_pmg_max;
+ u8 pmg_d, pmg_i;
+
+ saved_pmg_max = mpam_pmg_max;
+ saved_cdp = cdp_enabled;
+ saved_narrowing = resctrl_using_extra_partids;
+
+ mpam_pmg_max = param->pmg_max;
+ cdp_enabled = param->cdp_enabled;
+ resctrl_using_extra_partids = param->narrowing_enabled;
+
+ resctrl_closid_rmid_to_partid_pmg(param->closid, param->rmid,
+ &partid_d, &partid_i,
+ &pmg_d, &pmg_i);
+
+ KUNIT_EXPECT_EQ(test, partid_d, param->expected_partid_d);
+ KUNIT_EXPECT_EQ(test, partid_i, param->expected_partid_i);
+ KUNIT_EXPECT_EQ(test, pmg_d, param->expected_pmg_d);
+ KUNIT_EXPECT_EQ(test, pmg_i, param->expected_pmg_i);
+
+ mpam_pmg_max = saved_pmg_max;
+ cdp_enabled = saved_cdp;
+ resctrl_using_extra_partids = saved_narrowing;
+}
+
+static void test_cdp_narrowing_consistency(struct kunit *test)
+{
+ u32 partid_d1, partid_i1, partid_d2, partid_i2;
+ u8 pmg_d1, pmg_i1, pmg_d2, pmg_i2;
+ bool saved_cdp, saved_narrowing;
+ u8 saved_pmg_max;
+
+ saved_pmg_max = mpam_pmg_max;
+ saved_cdp = cdp_enabled;
+ saved_narrowing = resctrl_using_extra_partids;
+
+ mpam_pmg_max = 127;
+
+ cdp_enabled = true;
+ resctrl_using_extra_partids = false;
+ resctrl_closid_rmid_to_partid_pmg(10, 5, &partid_d1, &partid_i1,
+ &pmg_d1, &pmg_i1);
+
+ cdp_enabled = true;
+ resctrl_using_extra_partids = false;
+ resctrl_closid_rmid_to_partid_pmg(10, 7, &partid_d2, &partid_i2,
+ &pmg_d2, &pmg_i2);
+
+ KUNIT_EXPECT_EQ(test, partid_d1, partid_d2);
+ KUNIT_EXPECT_EQ(test, partid_i1, partid_i2);
+ KUNIT_EXPECT_EQ(test, partid_i1, partid_d1 + 1);
+
+ mpam_pmg_max = saved_pmg_max;
+ cdp_enabled = saved_cdp;
+ resctrl_using_extra_partids = saved_narrowing;
+}
+
+static void test_narrowing_rmid_encode_decode(struct kunit *test)
+{
+ bool saved_narrowing;
+ u8 pmg, decoded_pmg;
+ u8 saved_pmg_max;
+ u16 partid;
+ u32 rmid;
+
+ saved_pmg_max = mpam_pmg_max;
+ saved_narrowing = resctrl_using_extra_partids;
+
+ mpam_pmg_max = 127;
+ resctrl_using_extra_partids = true;
+
+ for (partid = 0; partid < 100; partid++) {
+ for (pmg = 0; pmg <= mpam_pmg_max; pmg += 10) {
+ rmid = partid * (mpam_pmg_max + 1) + pmg;
+
+ KUNIT_EXPECT_EQ(test, resctrl_rmid_to_partid(rmid), partid);
+ decoded_pmg = resctrl_rmid_to_pmg(rmid);
+ KUNIT_EXPECT_EQ(test, decoded_pmg, pmg);
+ }
+ }
+
+ mpam_pmg_max = saved_pmg_max;
+ resctrl_using_extra_partids = saved_narrowing;
+}
+
+static void test_narrowing_rmid_idx_encode(struct kunit *test)
+{
+ u32 closid, rmid, idx;
+ bool saved_narrowing;
+ u8 saved_pmg_max;
+
+ saved_pmg_max = mpam_pmg_max;
+ saved_narrowing = resctrl_using_extra_partids;
+
+ mpam_pmg_max = 127;
+
+ resctrl_using_extra_partids = false;
+ for (closid = 0; closid < 10; closid++) {
+ for (rmid = 0; rmid < 10; rmid++) {
+ idx = resctrl_arch_rmid_idx_encode(closid, rmid);
+ KUNIT_EXPECT_EQ(test, idx, closid * 128 + rmid);
+ }
+ }
+
+ resctrl_using_extra_partids = true;
+ for (closid = 0; closid < 10; closid++) {
+ for (rmid = 0; rmid < 1000; rmid += 100) {
+ idx = resctrl_arch_rmid_idx_encode(closid, rmid);
+ KUNIT_EXPECT_EQ(test, idx, rmid);
+ }
+ }
+
+ mpam_pmg_max = saved_pmg_max;
+ resctrl_using_extra_partids = saved_narrowing;
+}
+
+static void test_narrowing_rmid_idx_decode(struct kunit *test)
+{
+ u32 decoded_closid, decoded_rmid;
+ u32 idx, closid, rmid;
+ bool saved_narrowing;
+ u8 saved_pmg_max;
+
+ saved_pmg_max = mpam_pmg_max;
+ saved_narrowing = resctrl_using_extra_partids;
+
+ mpam_pmg_max = 127;
+
+ resctrl_using_extra_partids = false;
+ for (closid = 0; closid < 10; closid++) {
+ for (rmid = 0; rmid < 10; rmid++) {
+ idx = closid * 128 + rmid;
+ resctrl_arch_rmid_idx_decode(idx, &decoded_closid, &decoded_rmid);
+ KUNIT_EXPECT_EQ(test, decoded_closid, closid);
+ KUNIT_EXPECT_EQ(test, decoded_rmid, rmid);
+ }
+ }
+
+ resctrl_using_extra_partids = true;
+ mpam_partid_map_init(64, 32);
+ for (idx = 0; idx < 5000; idx += 500) {
+ u16 partid = resctrl_rmid_to_partid(idx);
+ u16 intpartid = mpam_partid_to_intpartid(partid);
+
+ resctrl_arch_rmid_idx_decode(idx, &decoded_closid, &decoded_rmid);
+ KUNIT_EXPECT_EQ(test, decoded_closid, intpartid);
+ KUNIT_EXPECT_EQ(test, decoded_rmid, idx);
+ }
+ mpam_partid_map_init(0, 0);
+
+ mpam_pmg_max = saved_pmg_max;
+ resctrl_using_extra_partids = saved_narrowing;
+}
+
+struct partid_mapping_case {
+ u16 partid;
+ u16 num_partid;
+ u16 num_closid;
+ u16 expected_intpartid;
+ const char *desc;
+};
+
+static const struct partid_mapping_case partid_mapping_cases[] = {
+ { 0, 128, 32, 0, "First PARTID maps to first intPARTID" },
+ { 31, 128, 32, 31, "Last intPARTID maps 1:1" },
+ { 32, 128, 32, 0, "First extra PARTID wraps to intPARTID 0" },
+ { 33, 128, 32, 1, "Second extra PARTID wraps to intPARTID 1" },
+ { 63, 128, 32, 31, "PARTID 63 wraps to intPARTID 31" },
+ { 64, 128, 32, 0, "PARTID 64 wraps to intPARTID 0" },
+ { 127, 128, 32, 31, "Max PARTID wraps to intPARTID 31" },
+ { 15, 64, 16, 15, "Small config: PARTID 15 → intPARTID 15" },
+ { 16, 64, 16, 0, "Small config: PARTID 16 wraps to 0" },
+ { 63, 64, 16, 15, "Small config: max PARTID wraps to 15" },
+};
+
+static void partid_mapping_desc(const struct partid_mapping_case *param,
+ char *desc)
+{
+ snprintf(desc, KUNIT_PARAM_DESC_SIZE, "%s", param->desc);
+}
+
+KUNIT_ARRAY_PARAM(partid_mapping, partid_mapping_cases, partid_mapping_desc);
+
+static void test_narrowing_partid_mapping(struct kunit *test)
+{
+ const struct partid_mapping_case *param = test->param_value;
+ u16 result;
+
+ mpam_partid_map_init(param->num_partid, param->num_closid);
+
+ result = mpam_partid_to_intpartid(param->partid);
+ KUNIT_EXPECT_EQ(test, result, param->expected_intpartid);
+
+ mpam_partid_map_init(0, 0);
+}
+
+struct partid_in_closid_case {
+ u16 partid;
+ u32 closid;
+ bool cdp_enabled;
+ bool narrowing_enabled;
+ u16 num_closid;
+ bool expected;
+ const char *desc;
+};
+
+static const struct partid_in_closid_case partid_in_closid_cases[] = {
+ { 10, 10, false, false, 32, true, "Legacy: exact match" },
+ { 10, 11, false, false, 32, false, "Legacy: no match" },
+ { 20, 10, true, false, 32, true, "CDP: even PARTID for closid 10" },
+ { 21, 10, true, false, 32, true, "CDP: odd PARTID for closid 10" },
+ { 22, 10, true, false, 32, false, "CDP: PARTID 22 not in closid 10" },
+ { 10, 10, false, true, 32, true, "Narrowing: PARTID 10 -> intPARTID 10" },
+ { 42, 10, false, true, 32, true, "Narrowing: PARTID 42 wraps to intPARTID 10" },
+ { 74, 10, false, true, 32, true, "Narrowing: PARTID 74 wraps to intPARTID 10" },
+ { 43, 10, false, true, 32, false, "Narrowing: PARTID 43 -> intPARTID 11" },
+ { 20, 10, true, true, 32, true, "CDP+narrow: even PARTID 20 -> intPARTID 10" },
+ { 21, 10, true, true, 32, true, "CDP+narrow: odd PARTID 21 -> intPARTID 10" },
+ { 84, 10, true, true, 32, true, "CDP+narrow: PARTID 84 wraps to intPARTID 10" },
+ { 86, 10, true, true, 32, false, "CDP+narrow: PARTID 86 -> intPARTID 11" },
+};
+
+static void partid_in_closid_desc(const struct partid_in_closid_case *param,
+ char *desc)
+{
+ snprintf(desc, KUNIT_PARAM_DESC_SIZE, "%s", param->desc);
+}
+
+KUNIT_ARRAY_PARAM(partid_in_closid, partid_in_closid_cases,
+ partid_in_closid_desc);
+
+static void test_narrowing_partid_in_closid(struct kunit *test)
+{
+ const struct partid_in_closid_case *param = test->param_value;
+ bool saved_cdp, saved_narrowing;
+ bool result;
+
+ saved_cdp = cdp_enabled;
+ saved_narrowing = resctrl_using_extra_partids;
+
+ cdp_enabled = param->cdp_enabled;
+ resctrl_using_extra_partids = param->narrowing_enabled;
+ mpam_partid_map_init(128, param->num_closid);
+
+ result = resctrl_partid_in_closid(param->partid, param->closid);
+ KUNIT_EXPECT_EQ(test, result, param->expected);
+
+ mpam_partid_map_init(0, 0);
+ cdp_enabled = saved_cdp;
+ resctrl_using_extra_partids = saved_narrowing;
+}
+
+static void test_narrowing_boundary_conditions(struct kunit *test)
+{
+ bool saved_narrowing;
+ u8 saved_pmg_max;
+ u32 rmid, idx;
+ u16 partid;
+ u8 pmg;
+
+ saved_pmg_max = mpam_pmg_max;
+ saved_narrowing = resctrl_using_extra_partids;
+
+ resctrl_using_extra_partids = true;
+
+ mpam_pmg_max = 255;
+ rmid = 255 * 256 + 255;
+ partid = resctrl_rmid_to_partid(rmid);
+ pmg = resctrl_rmid_to_pmg(rmid);
+ KUNIT_EXPECT_EQ(test, partid, 255);
+ KUNIT_EXPECT_EQ(test, pmg, 255);
+
+ mpam_pmg_max = 127;
+ rmid = 100 * 128 + 127;
+ partid = resctrl_rmid_to_partid(rmid);
+ pmg = resctrl_rmid_to_pmg(rmid);
+ KUNIT_EXPECT_EQ(test, partid, 100);
+ KUNIT_EXPECT_EQ(test, pmg, 127);
+
+ mpam_pmg_max = 0;
+ rmid = 50;
+ partid = resctrl_rmid_to_partid(rmid);
+ pmg = resctrl_rmid_to_pmg(rmid);
+ KUNIT_EXPECT_EQ(test, partid, 50);
+ KUNIT_EXPECT_EQ(test, pmg, 0);
+
+ mpam_pmg_max = 15;
+ idx = resctrl_arch_rmid_idx_encode(0, 1000);
+ KUNIT_EXPECT_EQ(test, idx, 1000);
+
+ mpam_pmg_max = saved_pmg_max;
+ resctrl_using_extra_partids = saved_narrowing;
+}
+
+static void test_narrowing_full_range_mapping(struct kunit *test)
+{
+ u16 partid, intpartid;
+ bool saved_narrowing;
+ u8 saved_pmg_max;
+ int count = 0;
+ u32 rmid;
+
+ saved_pmg_max = mpam_pmg_max;
+ saved_narrowing = resctrl_using_extra_partids;
+
+ mpam_pmg_max = 31;
+ resctrl_using_extra_partids = true;
+ mpam_partid_map_init(64, 16);
+
+ for (rmid = 0; rmid < 2048; rmid += 32) {
+ partid = resctrl_rmid_to_partid(rmid);
+ intpartid = mpam_partid_to_intpartid(partid);
+
+ KUNIT_EXPECT_LT(test, partid, 64);
+ KUNIT_EXPECT_LT(test, intpartid, 16);
+ KUNIT_EXPECT_EQ(test, intpartid, partid % 16);
+
+ count++;
+ }
+
+ KUNIT_EXPECT_GT(test, count, 0);
+
+ mpam_partid_map_init(0, 0);
+ mpam_pmg_max = saved_pmg_max;
+ resctrl_using_extra_partids = saved_narrowing;
+}
+
static struct kunit_case mpam_resctrl_test_cases[] = {
KUNIT_CASE(test_get_mba_granularity),
KUNIT_CASE_PARAM(test_mbw_max_to_percent, test_percent_value_gen_params),
@@ -304,6 +760,16 @@ static struct kunit_case mpam_resctrl_test_cases[] = {
KUNIT_CASE(test_percent_to_max_rounding),
KUNIT_CASE_PARAM(test_percent_max_roundtrip_stability,
test_all_bwa_wd_gen_params),
+ KUNIT_CASE_PARAM(test_cdp_narrowing_mapping, cdp_narrowing_gen_params),
+ KUNIT_CASE(test_cdp_narrowing_consistency),
+ KUNIT_CASE(test_narrowing_rmid_encode_decode),
+ KUNIT_CASE(test_narrowing_rmid_idx_encode),
+ KUNIT_CASE(test_narrowing_rmid_idx_decode),
+ KUNIT_CASE_PARAM(test_narrowing_partid_mapping, partid_mapping_gen_params),
+ KUNIT_CASE_PARAM(test_narrowing_partid_in_closid,
+ partid_in_closid_gen_params),
+ KUNIT_CASE(test_narrowing_boundary_conditions),
+ KUNIT_CASE(test_narrowing_full_range_mapping),
{}
};
--
2.34.1
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v10 RESEND 01/15] fs/resctrl: Return -EBUSY when per-CLOSID RMIDs are in limbo
2026-10-20 13:42 ` [PATCH v10 RESEND 01/15] fs/resctrl: Return -EBUSY when per-CLOSID RMIDs are in limbo Jinjie Ruan
@ 2026-10-26 14:05 ` Ben Horgan
0 siblings, 0 replies; 22+ messages in thread
From: Ben Horgan @ 2026-10-26 14:05 UTC (permalink / raw)
To: Jinjie Ruan, tony.luck, reinette.chatre, Dave.Martin,
james.morse, babu.moger, tglx, mingo, bp, dave.hansen, hpa,
fenghuay, tan.shaopeng, zengheng4, jic23, gshan, catalin.marinas,
x86, linux-kernel
Hi Jinjie,
On 20/10/2026 14:42, Jinjie Ruan wrote:
> resctrl_find_free_rmid() returns ERR_PTR(-ENOSPC) when no matching RMID
> is found in the free list. On systems where RMIDs depend on CLOSIDs
> (CONFIG_RESCTRL_RMID_DEPENDS_ON_CLOSID), the target CLOSID may have RMIDs
> temporarily in limbo rather than permanently exhausted.
>
> When userspace attempts to create a new monitoring group, a permanent
> -ENOSPC failure prevents retry even though the CLOSID's RMIDs may become
> available once the limbo handler completes.
>
> Check closid_num_dirty_rmid[] before returning -ENOSPC. If the target
> CLOSID has dirty RMIDs, return -EBUSY to signal a retryable condition.
>
> Fixes: c4c0376eefe1 ("x86/resctrl: Allow RMID allocation to be scoped by CLOSID")
> Cc: James Morse <james.morse@arm.com>
> Cc: Reinette Chatre <reinette.chatre@intel.com>
> Cc: Babu Moger <babu.moger@amd.com>
> Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
> ---
> fs/resctrl/monitor.c | 8 ++++++++
> 1 file changed, 8 insertions(+)
>
> diff --git a/fs/resctrl/monitor.c b/fs/resctrl/monitor.c
> index 73413cb128ea..da883adc3adc 100644
> --- a/fs/resctrl/monitor.c
> +++ b/fs/resctrl/monitor.c
> @@ -225,6 +225,14 @@ static struct rmid_entry *resctrl_find_free_rmid(u32 closid)
> return itr;
> }
>
> + /*
> + * If this CLOSID has RMIDs in limbo, return -EBUSY so userspace can
> + * retry. Otherwise, the CLOSID is genuinely out of RMIDs.
> + */
> + if (IS_ENABLED(CONFIG_RESCTRL_RMID_DEPENDS_ON_CLOSID) &&
> + closid_num_dirty_rmid[closid])
> + return ERR_PTR(-EBUSY);
> +
> return ERR_PTR(-ENOSPC);
> }
>
This seems like a sensible change to me. I ran into this myself the other day. For both cases the
last_cmd_status is "Out of RMIDs".
On a related, I have a patch, which I need to dig out and post, to add a flag so that the user can
know from the resctrl interface alone the maximum number of monitor groups can be created in a given
control group. It just displays the value of CONFIG_RESCTRL_RMID_DEPENDS_ON_CLOSID in
info/mon_id_includes_control_id.
For this patch of yours, and the next one, it would be good to post them with a series title with
the prefix 'fs/resctrl:' so that they are easily seen to be generic resctrl changes and discoverable
by the resctrl maintainers.
Thanks,
Ben
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v10 RESEND 02/15] fs/resctrl: Skip monitor context allocation when force-freeing RMIDs
2026-10-20 13:42 ` [PATCH v10 RESEND 02/15] fs/resctrl: Skip monitor context allocation when force-freeing RMIDs Jinjie Ruan
@ 2026-10-26 14:08 ` Ben Horgan
2026-10-26 14:11 ` Ben Horgan
0 siblings, 1 reply; 22+ messages in thread
From: Ben Horgan @ 2026-10-26 14:08 UTC (permalink / raw)
To: Jinjie Ruan, tony.luck, reinette.chatre, Dave.Martin,
james.morse, babu.moger, tglx, mingo, bp, dave.hansen, hpa,
fenghuay, tan.shaopeng, zengheng4, jic23, gshan, catalin.marinas,
x86, linux-kernel
Hi Jinjie,
On 20/10/2026 14:42, Jinjie Ruan wrote:
> __check_limbo() allocates the architecture monitor context before the
> scan loop, even when force_free is set. The force-free path never reads
> occupancy and never uses the context.
>
> If the allocation fails, the function returns early and leaves the limbo
> list unflushed. Callers such as the unmount path then silently fail to
> return the RMIDs to the free list, leaving stale CLOSIDs in limbo that
> are re-interpreted with the CDP state of the next mount.
Is this a theoretical fix or is there some way that resctrl_arch_mon_ctx_alloc() can fail in practice?
Thanks,
Ben
>
> Only allocate and free the monitor context when occupancy will be read.
>
> Fixes: fa7d949337cc ("x86/resctrl: Rename and move rdt files to a separate directory")
> Cc: James Morse <james.morse@arm.com>
> Cc: Reinette Chatre <reinette.chatre@intel.com>
> Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
> ---
> fs/resctrl/monitor.c | 15 +++++++++------
> 1 file changed, 9 insertions(+), 6 deletions(-)
>
> diff --git a/fs/resctrl/monitor.c b/fs/resctrl/monitor.c
> index da883adc3adc..ffa9c67c9f00 100644
> --- a/fs/resctrl/monitor.c
> +++ b/fs/resctrl/monitor.c
> @@ -142,11 +142,13 @@ void __check_limbo(struct rdt_l3_mon_domain *d, bool force_free)
> u64 val = 0;
>
> arch_priv = mon_event_all[QOS_L3_OCCUP_EVENT_ID].arch_priv;
> - arch_mon_ctx = resctrl_arch_mon_ctx_alloc(r, QOS_L3_OCCUP_EVENT_ID);
> - if (IS_ERR(arch_mon_ctx)) {
> - pr_warn_ratelimited("Failed to allocate monitor context: %pe",
> - arch_mon_ctx);
> - return;
> + if (!force_free) {
> + arch_mon_ctx = resctrl_arch_mon_ctx_alloc(r, QOS_L3_OCCUP_EVENT_ID);
> + if (IS_ERR(arch_mon_ctx)) {
> + pr_warn_ratelimited("Failed to allocate monitor context: %pe",
> + arch_mon_ctx);
> + return;
> + }
> }
>
> /*
> @@ -192,7 +194,8 @@ void __check_limbo(struct rdt_l3_mon_domain *d, bool force_free)
> cur_idx = idx + 1;
> }
>
> - resctrl_arch_mon_ctx_free(r, QOS_L3_OCCUP_EVENT_ID, arch_mon_ctx);
> + if (!force_free)
> + resctrl_arch_mon_ctx_free(r, QOS_L3_OCCUP_EVENT_ID, arch_mon_ctx);
> }
>
> bool has_busy_rmid(struct rdt_l3_mon_domain *d)
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v10 RESEND 02/15] fs/resctrl: Skip monitor context allocation when force-freeing RMIDs
2026-10-26 14:08 ` Ben Horgan
@ 2026-10-26 14:11 ` Ben Horgan
0 siblings, 0 replies; 22+ messages in thread
From: Ben Horgan @ 2026-10-26 14:11 UTC (permalink / raw)
To: Jinjie Ruan, tony.luck, reinette.chatre, Dave.Martin,
james.morse, babu.moger, tglx, mingo, bp, dave.hansen, hpa,
fenghuay, tan.shaopeng, zengheng4, jic23, gshan, catalin.marinas,
x86, linux-kernel
Hi Jinjie,
On 26/10/2026 14:08, Ben Horgan wrote:
> Hi Jinjie,
>
> On 20/10/2026 14:42, Jinjie Ruan wrote:
>> __check_limbo() allocates the architecture monitor context before the
>> scan loop, even when force_free is set. The force-free path never reads
>> occupancy and never uses the context.
>>
>> If the allocation fails, the function returns early and leaves the limbo
>> list unflushed. Callers such as the unmount path then silently fail to
>> return the RMIDs to the free list, leaving stale CLOSIDs in limbo that
>> are re-interpreted with the CDP state of the next mount.
>
> Is this a theoretical fix or is there some way that resctrl_arch_mon_ctx_alloc() can fail in practice?
This does seem a good change to me and we do have to consider that the memory alocation might fail.
Thanks,
Ben
>
> Thanks,
>
> Ben
>
>>
>> Only allocate and free the monitor context when occupancy will be read.
>>
>> Fixes: fa7d949337cc ("x86/resctrl: Rename and move rdt files to a separate directory")
>> Cc: James Morse <james.morse@arm.com>
>> Cc: Reinette Chatre <reinette.chatre@intel.com>
>> Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
>> ---
>> fs/resctrl/monitor.c | 15 +++++++++------
>> 1 file changed, 9 insertions(+), 6 deletions(-)
>>
>> diff --git a/fs/resctrl/monitor.c b/fs/resctrl/monitor.c
>> index da883adc3adc..ffa9c67c9f00 100644
>> --- a/fs/resctrl/monitor.c
>> +++ b/fs/resctrl/monitor.c
>> @@ -142,11 +142,13 @@ void __check_limbo(struct rdt_l3_mon_domain *d, bool force_free)
>> u64 val = 0;
>>
>> arch_priv = mon_event_all[QOS_L3_OCCUP_EVENT_ID].arch_priv;
>> - arch_mon_ctx = resctrl_arch_mon_ctx_alloc(r, QOS_L3_OCCUP_EVENT_ID);
>> - if (IS_ERR(arch_mon_ctx)) {
>> - pr_warn_ratelimited("Failed to allocate monitor context: %pe",
>> - arch_mon_ctx);
>> - return;
>> + if (!force_free) {
>> + arch_mon_ctx = resctrl_arch_mon_ctx_alloc(r, QOS_L3_OCCUP_EVENT_ID);
>> + if (IS_ERR(arch_mon_ctx)) {
>> + pr_warn_ratelimited("Failed to allocate monitor context: %pe",
>> + arch_mon_ctx);
>> + return;
>> + }
>> }
>>
>> /*
>> @@ -192,7 +194,8 @@ void __check_limbo(struct rdt_l3_mon_domain *d, bool force_free)
>> cur_idx = idx + 1;
>> }
>>
>> - resctrl_arch_mon_ctx_free(r, QOS_L3_OCCUP_EVENT_ID, arch_mon_ctx);
>> + if (!force_free)
>> + resctrl_arch_mon_ctx_free(r, QOS_L3_OCCUP_EVENT_ID, arch_mon_ctx);
>> }
>>
>> bool has_busy_rmid(struct rdt_l3_mon_domain *d)
>
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v10 RESEND 03/15] arm_mpam: Fix error return in resctrl_arch_mon_ctx_alloc()
2026-10-20 13:42 ` [PATCH v10 RESEND 03/15] arm_mpam: Fix error return in resctrl_arch_mon_ctx_alloc() Jinjie Ruan
@ 2026-10-26 14:40 ` Ben Horgan
0 siblings, 0 replies; 22+ messages in thread
From: Ben Horgan @ 2026-10-26 14:40 UTC (permalink / raw)
To: Jinjie Ruan, tony.luck, reinette.chatre, Dave.Martin,
james.morse, babu.moger, tglx, mingo, bp, dave.hansen, hpa,
fenghuay, tan.shaopeng, zengheng4, jic23, gshan, catalin.marinas,
x86, linux-kernel
Hi Jinjie,
On 20/10/2026 14:42, Jinjie Ruan wrote:
> resctrl_arch_mon_ctx_alloc() stores the allocation result in a
> heap-allocated int and returns the pointer unconditionally. When
> allocation fails with anything other than a retryable -ENOSPC (e.g.
> -EINVAL, -EOPNOTSUPP, or -ENOSPC with a pending signal), the caller
> receives a valid heap pointer that IS_ERR() does not detect.
>
> The caller then treats the error code as a monitor index: hardware
> reads use an out-of-range index, and resctrl_arch_mon_ctx_free() hands
> it to ida_free(), corrupting the allocator state.
>
> Return ERR_PTR() when the allocation failed.
Is this just theoretical? What state do we have to be in for resctrl_arch_mon_ctx_alloc_no_wait() to
fail? I see that the same fix has already been posted by Sang-Hoon.
https://lore.kernel.org/lkml/179075213853.3.16001626385793291460.idr-bug-33@gmail.com/
Thanks,
Ben
>
> Fixes: 2a3c79c61539 ("arm_mpam: resctrl: Allow resctrl to allocate monitors")
> Cc: James Morse <james.morse@arm.com>
> Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
> ---
> drivers/resctrl/mpam_resctrl.c | 7 +++++++
> 1 file changed, 7 insertions(+)
>
> diff --git a/drivers/resctrl/mpam_resctrl.c b/drivers/resctrl/mpam_resctrl.c
> index edd8da460fd7..23a8771ef148 100644
> --- a/drivers/resctrl/mpam_resctrl.c
> +++ b/drivers/resctrl/mpam_resctrl.c
> @@ -413,6 +413,13 @@ void *resctrl_arch_mon_ctx_alloc(struct rdt_resource *r,
> } while (*ret == -ENOSPC && !signal_pending(current));
> finish_wait(&resctrl_mon_ctx_waiters, &wait);
>
> + if (*ret < 0) {
> + int err = *ret;
> +
> + kfree(ret);
> + return ERR_PTR(err);
> + }
> +
> return ret;
> }
>
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v10 RESEND 05/15] arm_mpam: Don't stall MPAM enable when an MSC fails to probe
2026-10-20 13:42 ` [PATCH v10 RESEND 05/15] arm_mpam: Don't stall MPAM enable when an MSC fails to probe Jinjie Ruan
@ 2026-10-26 14:49 ` Ben Horgan
0 siblings, 0 replies; 22+ messages in thread
From: Ben Horgan @ 2026-10-26 14:49 UTC (permalink / raw)
To: Jinjie Ruan, tony.luck, reinette.chatre, Dave.Martin,
james.morse, babu.moger, tglx, mingo, bp, dave.hansen, hpa,
fenghuay, tan.shaopeng, zengheng4, jic23, gshan, catalin.marinas,
x86, linux-kernel
Hi Jinjie,
On 20/10/2026 14:42, Jinjie Ruan wrote:
> mpam_msc_drv_probe() registers the discovery cpuhp callbacks only when
> the number of successfully probed MSCs reaches fw_num_msc. Every failure
> path returns before the counter is incremented, and a failed probe is
> final - the driver core does not retry. One failing MSC therefore leaves
> the counter below fw_num_msc forever: the discovery callbacks are never
> registered, mpam_enable() is never scheduled, and MPAM stays dead
> system-wide - the resctrl filesystem is never registered, and no error
> message hints at the cause.
>
> An MSC can legitimately fail to probe on an otherwise working system:
> firmware may describe MSCs that are not accessible from any possible
> CPU, e.g. when the possible CPUs are restricted with nr_cpus=. The MSCs
> of the clipped CPUs then fail with "MSC is not accessible from any
> CPU!", or their cache resources become unresolvable from PPTT.
As the MPAM resources are system wide we need to query the support from each of the MSC to know what
resources will end up. I do appreciate that if you have full control of your system then you can
know this in advance but then you can also adjust your acpi tables to mark the non-accessible MSC as
disabled. As such, I don't think changes such as this should be in the driver. Here is a previous
explanation for a similar patch [1]
Thanks,
Ben
[1] https://lore.kernel.org/lkml/59c21340-4e3d-4c48-9d9f-c0f78a023bac@arm.com/
>
> Count every completed probe, successful or not. Failed MSCs are not on
> mpam_all_msc (either never added, or removed by mpam_msc_drv_remove()),
> so the discovery path skips them naturally, and MPAM is enabled for the
> surviving devices.
>
> Cc: stable@vger.kernel.org
> Cc: Ben Horgan <ben.horgan@arm.com>
> Cc: Reinette Chatre <reinette.chatre@intel.com>
> Cc: Fenghua Yu <fenghuay@nvidia.com>
> Cc: Shaopeng Tan <tan.shaopeng@jp.fujitsu.com>
> Cc: Catalin Marinas <catalin.marinas@arm.com>
> Fixes: f04046f2577a5 ("arm_mpam: Add probe/remove for mpam msc driver and kbuild boiler plate")
> Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
> ---
> drivers/resctrl/mpam_devices.c | 26 ++++++++++++++++++--------
> 1 file changed, 18 insertions(+), 8 deletions(-)
>
> diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
> index 61cbc75625fa..a44d66a5733d 100644
> --- a/drivers/resctrl/mpam_devices.c
> +++ b/drivers/resctrl/mpam_devices.c
> @@ -2120,21 +2120,31 @@ static int mpam_msc_drv_probe(struct platform_device *pdev)
> msc = do_mpam_msc_drv_probe(pdev);
> mutex_unlock(&mpam_list_lock);
>
> - if (IS_ERR(msc))
> - return PTR_ERR(msc);
> + if (IS_ERR(msc)) {
> + err = PTR_ERR(msc);
> + goto out;
> + }
>
> /* Create RIS entries described by firmware */
> err = acpi_mpam_parse_resources(msc, plat_data);
> - if (err) {
> + if (err)
> mpam_msc_drv_remove(pdev);
> - return err;
> - }
> -
> - if (atomic_add_return(1, &mpam_num_msc) == fw_num_msc)
> +out:
> + /*
> + * A failed MSC must not prevent MPAM being enabled for the
> + * surviving devices. Firmware may describe MSCs that are not
> + * accessible from any possible CPU, e.g. when the possible CPUs
> + * were restricted by nr_cpus=. The failed device is not
> + * re-probed, so count it here too: otherwise the counter never
> + * reaches fw_num_msc, the discovery cpuhp callbacks are never
> + * registered and mpam_enable() is never scheduled - MPAM stays
> + * dead system-wide, without any error message.
> + */
> + if (atomic_inc_return(&mpam_num_msc) == fw_num_msc)
> mpam_register_cpuhp_callbacks(mpam_discovery_cpu_online, NULL,
> "mpam:drv_probe");
>
> - return 0;
> + return err;
> }
>
> static struct platform_driver mpam_msc_driver = {
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v10 RESEND 06/15] fs/resctrl: Unmount resctrl with the CDP state it was mounted with
2026-10-20 13:42 ` [PATCH v10 RESEND 06/15] fs/resctrl: Unmount resctrl with the CDP state it was mounted with Jinjie Ruan
@ 2026-10-26 15:08 ` Ben Horgan
0 siblings, 0 replies; 22+ messages in thread
From: Ben Horgan @ 2026-10-26 15:08 UTC (permalink / raw)
To: Jinjie Ruan, tony.luck, reinette.chatre, Dave.Martin,
james.morse, babu.moger, tglx, mingo, bp, dave.hansen, hpa,
fenghuay, tan.shaopeng, zengheng4, jic23, gshan, catalin.marinas,
x86, linux-kernel
Hi Jinjie,
On 20/10/2026 14:42, Jinjie Ruan wrote:
> From: Zeng Heng <zengheng4@huawei.com>
>
> resctrl disables CDP before the control and monitor groups are torn down
> on unmount. The architecture translates between CLOSID and PARTID using
> the CDP state, so the RMIDs freed by rmdir_all_sub() and
> resctrl_fs_teardown() are parsed with the wrong mapping. On arm64 the
> RMID limbo handler can still be running, and a CLOSID left in the limbo
> list is re-interpreted with the CDP state of the next mount. Enabling
> CDP doubles the CLOSID on arm64, which can push the stale value out of
> range and take an MPAM error interrupt.
>
> Disable CDP after the groups have been torn down, so every CLOSID freed
> on unmount is parsed with the CDP state it was allocated under. Return
> the RMIDs still in limbo to the free list and stop the worker, so
> nothing carries over to the next mount.
>
> Cc: Tony Luck <tony.luck@intel.com>
> Cc: Reinette Chatre <reinette.chatre@intel.com>
> Cc: Dave Martin <Dave.Martin@arm.com>
> Cc: James Morse <james.morse@arm.com>
> Cc: Babu Moger <babu.moger@amd.com>
> Signed-off-by: Zeng Heng <zengheng4@huawei.com>
> Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
> ---
> fs/resctrl/rdtgroup.c | 46 +++++++++++++++++++++++++++++++++++++++++--
> 1 file changed, 44 insertions(+), 2 deletions(-)
>
> diff --git a/fs/resctrl/rdtgroup.c b/fs/resctrl/rdtgroup.c
> index 5dcbb0a964e8..40821cf07649 100644
> --- a/fs/resctrl/rdtgroup.c
> +++ b/fs/resctrl/rdtgroup.c
> @@ -3145,6 +3145,41 @@ static void resctrl_fs_teardown(void)
> rdtgroup_destroy_root();
> }
>
> +/*
> + * The RMID limbo handler keeps running after unmount. Any CLOSID still in
> + * limbo would be re-interpreted with the CDP state of the next mount. MPAM
> + * doubles CLOSID when CDP is enabled, which can push the stale value out of
> + * range. Force the limbo list back to the free list before the CLOSID state
> + * is torn down.
If the limbo list is being emptied then the limbo handler is effectively not being run. This is a
reduction of functionality of resctrl. We either need a way to keep it running or rebuild the list
at mount. There was some discussion at
https://lore.kernel.org/all/20260413085405.1166412-2-zengheng4@huawei.com/
This is a fs/resctrl patch that doesn't directly relate with the subject of the series and so
unlikely to get much attention from the resctrl maintainers. If you respin I'd suggest posting this
separately. I note that Tony Luck has a patch in the same area, [1].
[1] https://lore.kernel.org/lkml/20260928221509.68002-14-tony.luck@intel.com/
Thanks,
Ben
> + */
> +static void rdt_flush_limbo(void)
> +{
> + struct rdt_resource *r = resctrl_arch_get_resource(RDT_RESOURCE_L3);
> + struct rdt_l3_mon_domain *d;
> +
> + if (!IS_ENABLED(CONFIG_RESCTRL_RMID_DEPENDS_ON_CLOSID))
> + return;
> +
> + if (!resctrl_is_mon_event_enabled(QOS_L3_OCCUP_EVENT_ID))
> + return;
> +
> + list_for_each_entry(d, &r->mon_domains, hdr.list) {
> + if (!has_busy_rmid(d))
> + continue;
> +
> + /*
> + * The limbo handler acquires rdtgroup_mutex. Release it
> + * here to allow the handler to complete, then reacquire
> + * to maintain lock ordering for __check_limbo().
> + * This matches the pattern used in resctrl_offline_cpu().
> + */
> + mutex_unlock(&rdtgroup_mutex);
> + cancel_delayed_work_sync(&d->cqm_limbo);
> + mutex_lock(&rdtgroup_mutex);
> + __check_limbo(d, true);
> + }
> +}
> +
> static void resctrl_unmount(void)
> {
> struct rdt_resource *r;
> @@ -3152,13 +3187,20 @@ static void resctrl_unmount(void)
> cpus_read_lock();
> mutex_lock(&rdtgroup_mutex);
>
> - rdt_disable_ctx();
> -
> /* Put everything back to default values. */
> for_each_alloc_capable_rdt_resource(r)
> resctrl_arch_reset_all_ctrls(r);
>
> resctrl_fs_teardown();
> + rdt_flush_limbo();
> +
> + /*
> + * Clear the CDP state last. free_rmid() translates between CLOSID and
> + * PARTID using the CDP state; clearing it first would make the
> + * translation inconsistent for the RMIDs being torn down.
> + */
> + rdt_disable_ctx();
> +
> if (resctrl_arch_alloc_capable())
> resctrl_arch_disable_alloc();
> if (resctrl_arch_mon_capable())
^ permalink raw reply [flat|nested] 22+ messages in thread
end of thread, other threads:[~2026-10-26 15:09 UTC | newest]
Thread overview: 22+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-20 13:42 [PATCH v10 RESEND 00/15] arm_mpam: Introduce Narrow-PARTID feature Jinjie Ruan
2026-10-20 13:42 ` [PATCH v10 RESEND 01/15] fs/resctrl: Return -EBUSY when per-CLOSID RMIDs are in limbo Jinjie Ruan
2026-10-26 14:05 ` Ben Horgan
2026-10-20 13:42 ` [PATCH v10 RESEND 02/15] fs/resctrl: Skip monitor context allocation when force-freeing RMIDs Jinjie Ruan
2026-10-26 14:08 ` Ben Horgan
2026-10-26 14:11 ` Ben Horgan
2026-10-20 13:42 ` [PATCH v10 RESEND 03/15] arm_mpam: Fix error return in resctrl_arch_mon_ctx_alloc() Jinjie Ruan
2026-10-26 14:40 ` Ben Horgan
2026-10-20 13:42 ` [PATCH v10 RESEND 04/15] arm_mpam: Fix infinite PARTID reset loop when 65536 PARTIDs are supported Jinjie Ruan
2026-10-20 13:42 ` [PATCH v10 RESEND 05/15] arm_mpam: Don't stall MPAM enable when an MSC fails to probe Jinjie Ruan
2026-10-26 14:49 ` Ben Horgan
2026-10-20 13:42 ` [PATCH v10 RESEND 06/15] fs/resctrl: Unmount resctrl with the CDP state it was mounted with Jinjie Ruan
2026-10-26 15:08 ` Ben Horgan
2026-10-20 13:42 ` [PATCH v10 RESEND 07/15] fs/resctrl: Add resctrl_arch_rmid_idx_match() helper Jinjie Ruan
2026-10-20 13:42 ` [PATCH v10 RESEND 08/15] arm_mpam: resctrl: Add resctrl_closid_rmid_to_partid_pmg() helper Jinjie Ruan
2026-10-20 13:42 ` [PATCH v10 RESEND 09/15] arm_mpam: Add a helper to check a PARTID is in a CLOSID Jinjie Ruan
2026-10-20 13:42 ` [PATCH v10 RESEND 10/15] arm_mpam: Track the PARTID and internal PARTID limits per class Jinjie Ruan
2026-10-20 13:42 ` [PATCH v10 RESEND 11/15] arm_mpam: Divide the PARTIDs between the control groups Jinjie Ruan
2026-10-20 13:42 ` [PATCH v10 RESEND 12/15] arm_mpam: Map resctrl RMIDs onto the request PARTIDs Jinjie Ruan
2026-10-20 13:42 ` [PATCH v10 RESEND 13/15] arm_mpam: Identify the non-aliasing controls Jinjie Ruan
2026-10-20 13:42 ` [PATCH v10 RESEND 14/15] arm_mpam: Use the request PARTIDs beyond the internal range Jinjie Ruan
2026-10-20 13:42 ` [PATCH v10 RESEND 15/15] arm_mpam: resctrl: Add kunit tests for narrow PARTID helpers Jinjie Ruan
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®