[PATCH v2 18/21] sched/deadline: Credit a rebuild to a CPU that can hold the bandwidth

From: Mathieu Desnoyers

Date: Wed Nov 04 2026 - 19:37:02 EST


A rebuild of the root domains' accounting takes a CPU of the task's
cpuset for each deadline task it finds, dl_add_task_root_domain()
through dl_get_task_effective_cpus(), and two things can go wrong with
the CPUs isolated by isolcpu="domain", which belong to the default root
domain:

- the cpuset of a task that may run on a housekeeping CPU can include
isolated CPUs beside it, and without cpusets the mask is every CPU
the task can run on. The first active CPU of it is then an isolated
one where it comes first, and the task's bandwidth is counted in the
default root domain instead of the one it belongs to. The root
domain it belongs to underflows when the task leaves the class, or
dies;
- a task confined to isolated CPUs has them all taken offline, which
nothing refuses: a deadline task of the default root domain does not
keep the last of its CPUs online, dl_bw_deactivate() asking the root
domain of the CPU going offline. The rebuild then finds no active
CPU for it and BUG()s.

Leave the housekeeping CPUs of a task that has them, and leave a task
with no active CPU out of the rebuild: the generation of
"sched/deadline: Keep departing bandwidth across a root domain rebuild"
keeps the release of its bandwidth out as well, so that nothing is taken
from a root domain that no longer counts it, and the next rebuild
counts it again where it can.

Found by the Sashiko AI review of the first posting of this series.

Fixes: b4a61fdc19c5 ("sched/deadline: Fix stale root domain in cpuset bandwidth transfer")
Signed-off-by: Mathieu Desnoyers <mathieu.desnoyers@xxxxxxxxxxxx>
---
kernel/sched/deadline.c | 23 ++++++++++++++++++++++-
1 file changed, 22 insertions(+), 1 deletion(-)

diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c
index 029ec2ba5a66..c0af06d0f368 100644
--- a/kernel/sched/deadline.c
+++ b/kernel/sched/deadline.c
@@ -3617,6 +3617,16 @@ static void dl_get_task_effective_cpus(struct task_struct *p, struct cpumask *cp
* hierarchy up to the partition root.
*/
cpuset_cpus_allowed_locked(p, cpus);
+ /*
+ * Which the CPUs isolated by isolcpu="domain" are not part of: a
+ * cpuset can include them beside housekeeping CPUs, and without
+ * cpusets the mask is every CPU the task can run on. The first
+ * active CPU of it would then be an isolated one, of the default
+ * root domain, rather than one of the root domain the task's
+ * bandwidth belongs to.
+ */
+ if (housekeeping_enabled(HK_TYPE_DOMAIN))
+ cpumask_and(cpus, cpus, hk_msk);
}

/* The caller should hold cpuset_mutex */
@@ -3637,7 +3647,18 @@ void dl_add_task_root_domain(struct task_struct *p)
msk = this_cpu_cpumask_var_ptr(local_cpu_mask_dl);
dl_get_task_effective_cpus(p, msk);
cpu = cpumask_first_and(cpu_active_mask, msk);
- BUG_ON(cpu >= nr_cpu_ids);
+ if (cpu >= nr_cpu_ids) {
+ /*
+ * No active CPU to count this task's bandwidth: the ones it
+ * may run on all went offline, which the CPUs isolated by
+ * isolcpu="domain" allow, no deadline task of theirs keeping
+ * the last of them online. Leave it out of this rebuild,
+ * where dl_bw_gen keeps its release out as well, until one
+ * has a CPU for it.
+ */
+ raw_spin_unlock_irqrestore(&p->pi_lock, rf.flags);
+ return;
+ }

raw_spin_lock(&dl_departed_lock);
/*
--
2.47.3