[PATCH v4 3/3] fs/resctrl: Unmount resctrl with the CDP state it was mounted with
From: Jinjie Ruan
Date: Thu Nov 05 2026 - 02:11:22 EST
resctrl can be mounted without CDP, unmounted, and mounted again with
CDP enabled. A group's RMID is not returned to the free list when the
group is removed: it stays in limbo until the cache lines tagged with it
are evicted, and a limbo worker reads its occupancy counter in the
meantime. When resctrl is mounted again, the worker reads the RMIDs of
the groups removed by the unmount.
On MPAM the counter is selected by the PARTID of the CLOSID that owned
the RMID. Without CDP, CLOSID c is PARTID c; with CDP it is PARTIDs 2c
and 2c+1. resctrl_unmount() clears the CDP state before it removes the
groups, so their monitor configuration is also removed with a different
CLOSID to PARTID mapping. With 5 PARTIDs:
mount CDP off, CLOSID c -> PARTID c
mkdir g1..g4 CLOSIDs 1..4
umount
rdt_disable_ctx() clear CDP
remove g1..g4 RMIDs enter limbo with CLOSIDs 1..4
mount -o cdp CDP on, CLOSID c -> PARTIDs 2c, 2c+1
limbo worker reads CLOSID 2 as PARTID 5 > PARTID_MAX 4
MSC error interrupt, mpam_disable()
The groups are removed after rdt_disable_ctx() clears CDP, so their
monitor configuration is cleared with a CLOSID to PARTID mapping that
differs from the one used to set it up.
Clear the CDP state after the groups are removed. While resctrl is
unmounted, don't read the RMIDs in limbo. When resctrl is mounted,
return the RMIDs freed under the other CDP state to the free list
without reading their counters, then restart the limbo worker unless it
is already queued. The RMIDs stay in limbo across the unmount so that a
quick remount does not give a new group an RMID that cache lines from
the removed group still carry.
Only MPAM with CDP enabled through the cdp or cdpl2 mount option is
affected; CDP depends on CONFIG_EXPERT. x86 is not affected as its RMID
does not depend on the CLOSID.
Reproduced on QEMU with the sequence above:
mpam:__mpam_irq_handler: error irq from msc:1 'MSMONCFG_ID_RANGE', partid:5, pmg: 0, ris: 0
mpam:mpam_disable: MPAM disabled due to hardware error interrupt
With the change the sequence completes without an error.
Fixes: 6789fb99282c ("arm_mpam: resctrl: Add CDP emulation")
Signed-off-by: Jinjie Ruan <ruanjinjie@xxxxxxxxxx>
Suggested-by: Ben Horgan <ben.horgan@xxxxxxx>
Suggested-by: Reinette Chatre <reinette.chatre@xxxxxxxxx>
Cc: <stable@xxxxxxxxxxxxxxx> # 7.1+
Cc: Tony Luck <tony.luck@xxxxxxxxx>
Cc: Reinette Chatre <reinette.chatre@xxxxxxxxx>
Cc: Dave Martin <Dave.Martin@xxxxxxx>
Cc: James Morse <james.morse@xxxxxxx>
Cc: Babu Moger <babu.moger@xxxxxxx>
Link: https://lore.kernel.org/all/20261020134239.3921377-7-ruanjinjie@xxxxxxxxxx/
---
v4:
- Use resctrl_mounted instead of a separate limbo_paused flag, as the MBM
overflow handler does.
- Record the CDP state once when resctrl is mounted instead of on every
RMID that is freed.
- Add resctrl_limbo_resume(), called once resctrl_mounted is set.
- Treat CDP enabled on either cache as a change of the CDP state.
- Only release RMIDs where the RMID depends on the CLOSID, and skip
domains that have no occupancy bitmap.
- Restart the limbo worker only when it is not already queued.
- Start the limbo handler without a delay.
- Update cqm_work_cpu only when schedule_delayed_work_on() queued the
work, so it always names the CPU the work is queued on.
- Previous Link: https://lore.kernel.org/all/20261031094616.1691026-1-ruanjinjie@xxxxxxxxxx/
---
fs/resctrl/internal.h | 1 +
fs/resctrl/monitor.c | 71 ++++++++++++++++++++++++++++++++++++++-----
fs/resctrl/rdtgroup.c | 7 +++--
3 files changed, 70 insertions(+), 9 deletions(-)
diff --git a/fs/resctrl/internal.h b/fs/resctrl/internal.h
index 1c85e06f09bf..c95fa6ce62e6 100644
--- a/fs/resctrl/internal.h
+++ b/fs/resctrl/internal.h
@@ -373,6 +373,7 @@ void closid_free(int closid);
int setup_rmid_lru_list(void);
void free_rmid_lru_list(void);
+void resctrl_limbo_resume(void);
int alloc_rmid(u32 closid);
diff --git a/fs/resctrl/monitor.c b/fs/resctrl/monitor.c
index ae9ddd380515..717c3888c2b5 100644
--- a/fs/resctrl/monitor.c
+++ b/fs/resctrl/monitor.c
@@ -70,6 +70,11 @@ static u32 *closid_num_dirty_rmid;
*/
static unsigned int rmid_limbo_count;
+/*
+ * @limbo_cdp - The CDP state under which the RMIDs in limbo were freed.
+ */
+static bool limbo_cdp;
+
/*
* @rmid_entry - The entry in the limbo and free lists.
*/
@@ -852,6 +857,10 @@ void cqm_handle_limbo(struct work_struct *work)
*/
mutex_lock(&rdtgroup_mutex);
+ /* Don't read the RMIDs in limbo until a mount sets up the CDP state. */
+ if (IS_ENABLED(CONFIG_RESCTRL_RMID_DEPENDS_ON_CLOSID) && !resctrl_mounted)
+ goto out_unlock;
+
/*
* Ensure the worker is dedicated to a CPU as intended and not
* relocated by workqueue subsystem as part of CPU going offline.
@@ -868,10 +877,12 @@ void cqm_handle_limbo(struct work_struct *work)
__check_limbo(d);
if (has_busy_rmid(d)) {
- d->cqm_work_cpu = cpumask_any_housekeeping(&d->hdr.cpu_mask,
- RESCTRL_PICK_ANY_CPU);
- schedule_delayed_work_on(d->cqm_work_cpu, &d->cqm_limbo,
- delay);
+ int cpu;
+
+ cpu = cpumask_any_housekeeping(&d->hdr.cpu_mask,
+ RESCTRL_PICK_ANY_CPU);
+ if (schedule_delayed_work_on(cpu, &d->cqm_limbo, delay))
+ d->cqm_work_cpu = cpu;
}
out_unlock:
@@ -893,10 +904,13 @@ void cqm_setup_limbo_handler(struct rdt_l3_mon_domain *dom, unsigned long delay_
int cpu;
cpu = cpumask_any_housekeeping(&dom->hdr.cpu_mask, exclude_cpu);
- dom->cqm_work_cpu = cpu;
+ if (cpu >= nr_cpu_ids) {
+ dom->cqm_work_cpu = cpu;
+ return;
+ }
- if (cpu < nr_cpu_ids)
- schedule_delayed_work_on(cpu, &dom->cqm_limbo, delay);
+ if (schedule_delayed_work_on(cpu, &dom->cqm_limbo, delay))
+ dom->cqm_work_cpu = cpu;
}
void mbm_handle_overflow(struct work_struct *work)
@@ -1033,6 +1047,49 @@ int setup_rmid_lru_list(void)
return 0;
}
+/**
+ * resctrl_limbo_resume() - Resume RMID limbo handling after a mount.
+ *
+ * Release the RMIDs that were freed under another CDP state without
+ * reading them, and restart the limbo handler.
+ */
+void resctrl_limbo_resume(void)
+{
+ struct rdt_l3_mon_domain *d;
+ struct rdt_resource *r;
+ bool cdp;
+
+ if (!IS_ENABLED(CONFIG_RESCTRL_RMID_DEPENDS_ON_CLOSID) ||
+ !resctrl_arch_mon_capable())
+ return;
+
+ lockdep_assert_held(&rdtgroup_mutex);
+
+ r = resctrl_arch_get_resource(RDT_RESOURCE_L3);
+
+ cdp = resctrl_arch_get_cdp_enabled(RDT_RESOURCE_L2) ||
+ resctrl_arch_get_cdp_enabled(RDT_RESOURCE_L3);
+
+ if (cdp != limbo_cdp) {
+ list_for_each_entry_rcu(d, &r->mon_domains, hdr.list,
+ lockdep_is_cpus_held()) {
+ if (d->rmid_busy_llc && has_busy_rmid(d))
+ release_limbo_rmids(d);
+ }
+ }
+ limbo_cdp = cdp;
+
+ if (!resctrl_is_mon_event_enabled(QOS_L3_OCCUP_EVENT_ID))
+ return;
+
+ list_for_each_entry_rcu(d, &r->mon_domains, hdr.list,
+ lockdep_is_cpus_held()) {
+ /* A queued worker will run, don't move its cqm_work_cpu. */
+ if (has_busy_rmid(d) && !delayed_work_pending(&d->cqm_limbo))
+ cqm_setup_limbo_handler(d, 0, RESCTRL_PICK_ANY_CPU);
+ }
+}
+
void free_rmid_lru_list(void)
{
if (!resctrl_arch_mon_capable())
diff --git a/fs/resctrl/rdtgroup.c b/fs/resctrl/rdtgroup.c
index 5d0351453c2f..e6b64a5c9302 100644
--- a/fs/resctrl/rdtgroup.c
+++ b/fs/resctrl/rdtgroup.c
@@ -3150,13 +3150,14 @@ 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();
+
+ /* Remove the groups under the CDP state they were set up with. */
+ rdt_disable_ctx();
if (resctrl_arch_alloc_capable())
resctrl_arch_disable_alloc();
if (resctrl_arch_mon_capable())
@@ -3261,6 +3262,8 @@ static int rdt_get_tree(struct fs_context *fc)
RESCTRL_PICK_ANY_CPU);
}
+ resctrl_limbo_resume();
+
/*
* Ensure root remains accessible after mutex is unlocked so that
* kernfs_kill_sb() can run safely if called by kernfs_get_tree()'s
--
2.34.1