[RFC PATCH 3/6] fs/resctrl: Take the CPU hotplug lock for software controller updates

From: Jinjie Ruan

Date: Mon Nov 09 2026 - 08:27:31 EST


From: Jinjie Ruan <ruanjinjie@xxxxxxxxxx>

update_mba_bw() runs from the MBM overflow worker and calls the
architecture to read and write the control of the domain of its CPU,
under the RCU read lock.

Writing the control can sleep: MPAM uses smp_call_on_cpu() and waits
for a CPU that reaches the MSC, so that CPU must stay online. The
hotplug lock cannot be taken after rdtgroup_mutex, because the offline
path takes the hotplug lock first, and sleeping in the RCU read-side
section is invalid.

Take the hotplug lock with cpus_read_trylock() before rdtgroup_mutex.
Skip a round when it is not available; a blocking lock would deadlock
against the offline path, which holds the write lock while it drains
this worker. With the lock held the control domain cannot be removed,
so drop the RCU read lock. Where the update does not sleep, a round is
only skipped while hotplug runs.

Signed-off-by: Jinjie Ruan <ruanjinjie@xxxxxxxxxx>
---
fs/resctrl/monitor.c | 28 +++++++++++++++++++---------
fs/resctrl/rdtgroup.c | 8 +++++---
2 files changed, 24 insertions(+), 12 deletions(-)

diff --git a/fs/resctrl/monitor.c b/fs/resctrl/monitor.c
index 2319e8e619bd..76990bb80ee9 100644
--- a/fs/resctrl/monitor.c
+++ b/fs/resctrl/monitor.c
@@ -640,19 +640,18 @@ void mon_event_count(void *info)
/*
* Find the software controller's ctrl domain that contains @cpu on resource @r.
*
- * Only called from the mbm_over worker via update_mba_bw() where the returned
- * domain is kept alive by cancel_delayed_work_sync() in
- * resctrl_offline_ctrl_domain(). This drains this worker and then waits on
- * rdtgroup_mutex held here before the architecture can free the ctrl domain.
+ * Only called from the mbm_over worker via update_mba_bw(), which holds the
+ * CPU hotplug lock so the domain cannot be removed while it is being used.
*
- * Context: Call from RCU read-side critical section.
+ * Context: Call with the CPU hotplug lock held.
*/
static struct rdt_ctrl_domain *get_sc_ctrl_domain_from_cpu(int cpu,
struct rdt_resource *r)
{
struct rdt_ctrl_domain *d;

- list_for_each_entry_rcu(d, &r->ctrl_domains, hdr.list) {
+ list_for_each_entry_rcu(d, &r->ctrl_domains, hdr.list,
+ lockdep_is_cpus_held()) {
/* Find the domain that contains this CPU */
if (cpumask_test_cpu(cpu, &d->hdr.cpu_mask))
return d;
@@ -713,7 +712,8 @@ static void update_mba_bw(struct rdtgroup *rgrp, struct rdt_l3_mon_domain *dom_m
if (WARN_ON_ONCE(!pmbm_data))
return;

- guard(rcu)();
+ lockdep_assert_cpus_held();
+
dom_mba = get_sc_ctrl_domain_from_cpu(smp_processor_id(), r_mba);
if (!dom_mba) {
pr_warn_once("Failure to get domain for MBA update\n");
@@ -882,11 +882,19 @@ void mbm_handle_overflow(struct work_struct *work)
struct rdt_l3_mon_domain *d;
struct list_head *head;
struct rdt_resource *r;
+ bool cpus_read_locked = false;

/*
- * Safe to run without CPU hotplug lock. Work is guaranteed to be
+ * Reading counters is safe without the CPU hotplug lock: the work is
* canceled before the domain structure is removed.
+ *
+ * Programming the control can sleep and may need the hotplug lock,
+ * which cannot be taken after rdtgroup_mutex. Try it first; if
+ * hotplug is in progress skip the update and retry next round.
*/
+ if (is_mba_sc(NULL))
+ cpus_read_locked = cpus_read_trylock();
+
mutex_lock(&rdtgroup_mutex);

/*
@@ -921,7 +929,7 @@ void mbm_handle_overflow(struct work_struct *work)
list_for_each_entry(crgrp, head, mon.crdtgrp_list)
mbm_update(r, d, crgrp);

- if (is_mba_sc(NULL))
+ if (is_mba_sc(NULL) && cpus_read_locked)
update_mba_bw(prgrp, d);
}

@@ -935,6 +943,8 @@ void mbm_handle_overflow(struct work_struct *work)

out_unlock:
mutex_unlock(&rdtgroup_mutex);
+ if (cpus_read_locked)
+ cpus_read_unlock();
}

/**
diff --git a/fs/resctrl/rdtgroup.c b/fs/resctrl/rdtgroup.c
index 57ab090072c2..b32831871f70 100644
--- a/fs/resctrl/rdtgroup.c
+++ b/fs/resctrl/rdtgroup.c
@@ -4519,9 +4519,11 @@ void resctrl_offline_ctrl_domain(struct rdt_resource *r, struct rdt_ctrl_domain
* no new worker iteration can find it; drain any worker that already
* holds a pointer to it before the architecture frees the domain.
*
- * Software controller is enabled/disabled on mount/unmount with
- * cpus_read_lock() held. Running here with cpus_write_lock() so
- * there are no concurrent changes to software controller status.
+ * The overflow worker holds the CPU hotplug read lock around its
+ * software controller updates, and mount/unmount enable/disable the
+ * software controller with the read lock held. Running here with
+ * cpus_write_lock() means there are no in-flight software controller
+ * updates and no concurrent change of the software controller status.
*/
if (r->rid == RDT_RESOURCE_MBA && is_mba_sc(r)) {
struct rdt_resource *l3 = resctrl_arch_get_resource(RDT_RESOURCE_L3);
--
2.43.0