[RFC PATCH 3/3] sched/topology: Tear down domains without active domain housekeeping CPUs
From: Guopeng Zhang
Date: Wed Jul 22 2026 - 08:03:06 EST
From: Guopeng Zhang <zhangguopeng@xxxxxxxxxx>
During suspend, freeze_secondary_cpus() takes every CPU except the primary
offline. If the primary CPU is excluded from HK_TYPE_DOMAIN, offlining the
last domain housekeeping CPU leaves no active CPU from which to build the
fallback scheduler domain.
The frozen CPU-hotplug callback nevertheless requests one domain by
passing a non-zero domain count with a NULL domain array.
partition_sched_domains_locked() then constructs the fallback span from
cpu_active_mask and housekeeping_cpumask(HK_TYPE_DOMAIN), which is empty.
build_sched_domains() warns about the empty span, followed by a general
protection fault in build_perf_domains().
Make cpuset_reset_sched_domains(), including its !CONFIG_CPUSETS stub,
request zero domains when no active HK_TYPE_DOMAIN CPU remains.
Distinguish that explicit zero-domain request from a non-zero request with
a NULL domain array, for which the existing fallback-domain behavior must
be retained.
Fixes: d35be8bab9b0 ("CPU hotplug, cpusets, suspend: Don't modify cpusets during suspend/resume")
Signed-off-by: Guopeng Zhang <zhangguopeng@xxxxxxxxxx>
---
include/linux/cpuset.h | 7 ++++++-
kernel/cgroup/cpuset.c | 6 +++++-
kernel/sched/topology.c | 9 ++++-----
3 files changed, 15 insertions(+), 7 deletions(-)
diff --git a/include/linux/cpuset.h b/include/linux/cpuset.h
index a67ba1b32d16..461ba4876a8e 100644
--- a/include/linux/cpuset.h
+++ b/include/linux/cpuset.h
@@ -10,6 +10,7 @@
*/
#include <linux/sched.h>
+#include <linux/sched/isolation.h>
#include <linux/sched/topology.h>
#include <linux/sched/task.h>
#include <linux/cpumask.h>
@@ -284,7 +285,11 @@ static inline void rebuild_sched_domains(void)
static inline void cpuset_reset_sched_domains(void)
{
- partition_sched_domains(1, NULL, NULL);
+ if (cpumask_intersects(cpu_active_mask,
+ housekeeping_cpumask(HK_TYPE_DOMAIN)))
+ partition_sched_domains(1, NULL, NULL);
+ else
+ partition_sched_domains(0, NULL, NULL);
}
static inline void cpuset_print_current_mems_allowed(void)
diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c
index 03e82d6026f5..1e66723e01b4 100644
--- a/kernel/cgroup/cpuset.c
+++ b/kernel/cgroup/cpuset.c
@@ -1076,7 +1076,11 @@ void rebuild_sched_domains(void)
void cpuset_reset_sched_domains(void)
{
mutex_lock(&cpuset_mutex);
- partition_sched_domains(1, NULL, NULL);
+ if (cpumask_intersects(cpu_active_mask,
+ housekeeping_cpumask(HK_TYPE_DOMAIN)))
+ partition_sched_domains(1, NULL, NULL);
+ else
+ partition_sched_domains(0, NULL, NULL);
mutex_unlock(&cpuset_mutex);
}
diff --git a/kernel/sched/topology.c b/kernel/sched/topology.c
index 622e2e01974c..e8d34cb1ed84 100644
--- a/kernel/sched/topology.c
+++ b/kernel/sched/topology.c
@@ -3365,9 +3365,8 @@ static int dattrs_equal(struct sched_domain_attr *cur, int idx_cur,
* and partition_sched_domains() will fallback to the single partition
* 'fallback_doms', it also forces the domains to be rebuilt.
*
- * If doms_new == NULL it will be replaced with cpu_online_mask.
- * ndoms_new == 0 is a special case for destroying existing domains,
- * and it will not create the default domain.
+ * ndoms_new == 0 and doms_new == NULL is a special case for destroying
+ * existing domains, and it will not create the default domain.
*
* Call with hotplug lock and sched_domains_mutex held
*/
@@ -3387,7 +3386,7 @@ static void partition_sched_domains_locked(int ndoms_new, cpumask_var_t doms_new
if (new_topology)
asym_cpu_capacity_scan();
- if (!doms_new) {
+ if (ndoms_new && !doms_new) {
WARN_ON_ONCE(dattr_new);
n = 0;
doms_new = alloc_sched_domains(1);
@@ -3414,7 +3413,7 @@ static void partition_sched_domains_locked(int ndoms_new, cpumask_var_t doms_new
}
n = ndoms_cur;
- if (!doms_new) {
+ if (ndoms_new && !doms_new) {
n = 0;
doms_new = &fallback_doms;
cpumask_and(doms_new[0], cpu_active_mask,
--
2.43.0