All of lore.kernel.org
 help / color / mirror / Atom feed
From: Ridong Chen <ridong.chen@linux.dev>
To: "Waiman Long" <longman@redhat.com>, "Tejun Heo" <tj@kernel.org>,
	"Johannes Weiner" <hannes@cmpxchg.org>,
	"Michal Koutný" <mkoutny@suse.com>
Cc: Ridong Chen <ridong.chen@linux.dev>,
	Guopeng Zhang <zhangguopeng@kylinos.cn>,
	cgroups@vger.kernel.org, linux-kernel@vger.kernel.org,
	Ridong Chen <chenridong@xiaomi.com>
Subject: [PATCH v5 11/12] cpuset: introduce local_partition_update()
Date: Sat, 14 Nov 2026 16:31:23 +0800	[thread overview]
Message-ID: <20261114083124.1340140-12-ridong.chen@linux.dev> (raw)
In-Reply-To: <20261114083124.1340140-1-ridong.chen@linux.dev>

From: Ridong Chen <chenridong@xiaomi.com>

Factor the partcmd_update handling without a new cpumask out of
update_parent_effective_cpumask() into local_partition_update(), and
the one with a new cpumask, used by partition_cpus_change(), into
__local_partition_update().

__local_partition_update() validates the new effective exclusive CPUs
with validate_local_partition(), then:

 - if the validation fails, a valid partition is invalidated with
   partition_disable(), while an invalid partition only has its error
   code updated. The error code is kept if the parent is not a valid
   partition root, e.g. for a remote partition invalidated by hotplug,
   as the cpuset can't become a valid local partition anyway.
 - if the validation passes, an invalid partition becomes valid again
   with partition_enable(), and a valid partition has its CPUs updated
   with partition_update().

local_partition_update() computes the new effective exclusive CPUs
with compute_excpus() as before. As in the old partcmd_update path, the
sibling exclusivity check is only done for an invalid partition with an
empty cpuset.cpus.exclusive, as a valid partition has passed it
already.

cpuset_hotplug_update_tasks() now calls local_partition_update() for
both the invalidation and the revival of a local partition, instead of
open coding the invalidation conditions. The only exception is when
subpartitions_cpus becomes empty because no CPU is left for
top_cpuset, in which case the local partition is disabled directly
with PERR_HOTPLUG. A remote partition only needs the hotplug
invalidation check, so jump to update_tasks right after it. As
partition_disable() gives the CPUs back to the parent and updates
effective_cpus of the cpuset directly, effective_cpus is saved before
any partition state change and the new effective cpumask is compared
against it. Otherwise the tasks of a partition invalidated by hotplug
may be left with only the offlined CPUs.

In update_cpumasks_hier(), effective_cpus of the child is now updated
before local_partition_update() is called, as tmp->new_cpus is reused
by it. For the same reason, the effective cpumask is always recomputed
afterwards.

Signed-off-by: Ridong Chen <chenridong@xiaomi.com>
---
 kernel/cgroup/cpuset.c | 201 +++++++++++++++++++++++++++++------------
 1 file changed, 143 insertions(+), 58 deletions(-)

diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c
index 038bed1f4fb8..1eb7d11e1cbf 100644
--- a/kernel/cgroup/cpuset.c
+++ b/kernel/cgroup/cpuset.c
@@ -2084,6 +2084,100 @@ static int local_partition_enable(struct cpuset *cs,
 	return 0;
 }
 
+/**
+ * __local_partition_update - Update local partition configuration
+ * @cs: Target cpuset to update
+ * @xcpus: New exclusive CPU mask
+ * @excpus: New effective exclusive CPU mask
+ * @tmp: Temporary mask storage for intermediate calculations
+ * @excl_check: Flag to enable exclusivity validation
+ *
+ * Handles updates to local CPU partition configurations by validating
+ * changes, managing state transitions, and propagating updates through
+ * the cpuset hierarchy.
+ *
+ * Note on exclusivity checking: Exclusivity validation is required when
+ * transitioning from an invalid to valid partition state. However, when
+ * updating cpus_allowed or exclusive_cpus, exclusivity should have already
+ * been verified by validate_change(). In such cases, excl_check must be
+ * false since the cs cpumasks are not yet updated.
+ *
+ * Return: Partition error code (PERR_NONE indicates success)
+ */
+static int __local_partition_update(struct cpuset *cs, struct cpumask *xcpus,
+				    struct cpumask *excpus, struct tmpmasks *tmp,
+				    bool excl_check)
+{
+	struct cpuset *parent = parent_cs(cs);
+	int part_error = PERR_NONE;	/* Partition error? */
+	int old_prs, new_prs;
+
+	lockdep_assert_cpuset_lock_held();
+	/* For local partition only */
+	if (WARN_ON_ONCE(is_remote_partition(cs) || cs_is_member(cs)))
+		return PERR_NONE;
+
+	old_prs = cs->partition_root_state;
+	/*
+	 * If new_prs < 0, it might transition to valid partition state.
+	 * Use absolute value for validation checks.
+	 */
+	new_prs = old_prs < 0 ? -old_prs : old_prs;
+	cpumask_andnot(tmp->addmask, excpus, cs->effective_xcpus);
+	cpumask_andnot(tmp->delmask, cs->effective_xcpus, excpus);
+	part_error = validate_local_partition(cs, new_prs, excpus,
+					      excl_check, tmp);
+	if (part_error) {
+		/*
+		 * An invalid partition only needs to update its error code.
+		 * Keep the error code if the parent is not a valid partition
+		 * root, e.g. for an invalidated remote partition, as the
+		 * cpuset can't become a valid local partition anyway.
+		 */
+		if (is_partition_valid(cs))
+			partition_disable(cs, -new_prs, part_error, tmp);
+		else if (is_partition_valid(parent))
+			WRITE_ONCE(cs->prs_err, part_error);
+		return part_error;
+	}
+
+	/* Nothing changes, return PERR_NONE */
+	if (new_prs == old_prs &&
+	    (cpumask_empty(tmp->addmask) && cpumask_empty(tmp->delmask)))
+		return PERR_NONE;
+
+	/*
+	 * If partition was previously invalid but now passes checks,
+	 * re-enable it and update related flags.
+	 * Otherwise, partition state doesn't change, only cpumasks change.
+	 */
+	if (is_partition_invalid(cs)) {
+		partition_enable(cs, parent, new_prs, excpus);
+		update_partition_sd_lb(cs, old_prs);
+	} else {
+		partition_update(cs, new_prs, xcpus, excpus, tmp);
+	}
+
+	cpuset_update_tasks_cpumask(parent, tmp->addmask);
+	update_sibling_cpumasks(parent, cs, tmp);
+	return PERR_NONE;
+}
+
+static int local_partition_update(struct cpuset *cs, struct tmpmasks *tmp)
+{
+	/*
+	 * A valid partition has passed the exclusivity check already. An
+	 * invalid partition has to pass it before becoming valid again when
+	 * cpuset.cpus.exclusive is empty.
+	 */
+	bool excl_check = is_partition_invalid(cs) &&
+			  cpumask_empty(cs->exclusive_cpus);
+
+	compute_excpus(cs, tmp->new_cpus);
+	return __local_partition_update(cs, NULL, tmp->new_cpus, tmp,
+					excl_check);
+}
+
 /**
  * update_parent_effective_cpumask - update effective_cpus mask of parent cpuset
  * @cs:      The cpuset that requests change in partition root state
@@ -2121,9 +2215,9 @@ static int local_partition_enable(struct cpuset *cs,
  * check for error and so partition_root_state and prs_err will be updated
  * directly.
  */
-static int update_parent_effective_cpumask(struct cpuset *cs, int cmd,
-					   struct cpumask *newmask,
-					   struct tmpmasks *tmp)
+static int __maybe_unused update_parent_effective_cpumask(struct cpuset *cs, int cmd,
+							  struct cpumask *newmask,
+							  struct tmpmasks *tmp)
 {
 	struct cpuset *parent = parent_cs(cs);
 	int adding;	/* Adding cpus to parent's effective_cpus	*/
@@ -2143,23 +2237,6 @@ static int update_parent_effective_cpumask(struct cpuset *cs, int cmd,
 	adding = deleting = false;
 	old_prs = new_prs = cs->partition_root_state;
 
-	if (cmd == partcmd_invalidate) {
-		if (is_partition_invalid(cs))
-			return 0;
-
-		/*
-		 * Make the current partition invalid.
-		 */
-		if (is_partition_valid(parent))
-			adding = cpumask_and(tmp->addmask,
-					     cs->effective_xcpus,
-					     parent->effective_xcpus);
-		if (old_prs > 0)
-			new_prs = -old_prs;
-
-		goto write_error;
-	}
-
 	/*
 	 * The parent must be a partition root.
 	 * The new cpumask, if present, or the current cpus_allowed must
@@ -2527,14 +2604,21 @@ static void update_cpumasks_hier(struct cpuset *cs, struct tmpmasks *tmp,
 		if (!css_tryget_online(&cp->css))
 			continue;
 		rcu_read_unlock();
+		/*
+		 * The tmp->new_cpus may be modified.
+		 * Update effective_cpus before passing tmp to other functions.
+		 */
+		spin_lock_irq(&callback_lock);
+		cpumask_copy(cp->effective_cpus, tmp->new_cpus);
+		spin_unlock_irq(&callback_lock);
 
 		if (update_parent) {
-			update_parent_effective_cpumask(cp, partcmd_update, NULL, tmp);
+			local_partition_update(cp, tmp);
 			/*
 			 * Capture current partition_root_state as it may have
 			 * changed. Always recompute effective cpumask as tmp->new_cpus
 			 * may be clobbered when update_sibling_cpumasks() is called
-			 * within update_parent_effective_cpumask().
+			 * within local_partition_update().
 			 */
 			new_prs = cp->partition_root_state;
 			compute_effective_cpumask(tmp->new_cpus, cp, parent);
@@ -2693,8 +2777,8 @@ static void partition_cpus_change(struct cpuset *cs, struct cpuset *trialcs,
 					   trialcs->effective_xcpus, tmp);
 	} else {
 		if (!trialcs->prs_err)
-			update_parent_effective_cpumask(cs, partcmd_update,
-							trialcs->effective_xcpus, tmp);
+			__local_partition_update(cs, trialcs->exclusive_cpus,
+						 trialcs->effective_xcpus, tmp, false);
 		else if (is_partition_valid(cs))
 			partition_disable(cs, -cs->partition_root_state,
 					  trialcs->prs_err, tmp);
@@ -4149,61 +4233,62 @@ void cpuset_force_rebuild(void)
  */
 static void cpuset_hotplug_update_tasks(struct cpuset *cs, struct tmpmasks *tmp)
 {
-	static cpumask_t new_cpus;
+	static cpumask_t new_cpus, old_cpus;
 	static nodemask_t new_mems;
 	bool cpus_updated;
 	bool mems_updated;
-	int partcmd = -1;
 	struct cpuset *parent;
 
 	wait_attach_done_lock();
 	parent = parent_cs(cs);
+	/*
+	 * Partition state changes below may update effective_cpus directly,
+	 * save it to check if tasks need to be updated.
+	 */
+	cpumask_copy(&old_cpus, cs->effective_cpus);
 	compute_effective_cpumask(&new_cpus, cs, parent);
 	compute_effective_nodemask(&new_mems, cs, parent);
 
 	if (!tmp || !cs->partition_root_state)
 		goto update_tasks;
 
-	if (is_remote_partition(cs) && (cpumask_empty(subpartitions_cpus) ||
-				       (cpumask_empty(&new_cpus) &&
-					partition_is_populated(cs, NULL)))) {
-		WRITE_ONCE(cs->prs_err, PERR_HOTPLUG);
-		partition_disable(cs, -cs->partition_root_state, cs->prs_err,
-				  tmp);
-		compute_effective_cpumask(&new_cpus, cs, parent);
+	/*
+	 * Compute effective_cpus for valid partition root, may invalidate
+	 * child partition roots if necessary.
+	 */
+	if (is_remote_partition(cs)) {
+		if (cpumask_empty(subpartitions_cpus) ||
+		    (cpumask_empty(&new_cpus) &&
+		     partition_is_populated(cs, NULL))) {
+			WRITE_ONCE(cs->prs_err, PERR_HOTPLUG);
+			partition_disable(cs, -cs->partition_root_state,
+					  cs->prs_err, tmp);
+			compute_effective_cpumask(&new_cpus, cs, parent);
+		}
+		goto update_tasks;
 	}
 
 	/*
-	 * Force the partition to become invalid if either one of
-	 * the following conditions hold:
-	 * 1) empty effective cpus but not valid empty partition.
-	 * 2) parent is invalid or doesn't grant any cpus to child
-	 *    partitions.
-	 * 3) subpartitions_cpus is empty.
+	 * The subpartitions_cpus may be cleared if no CPUs remain available for
+	 * the top_cpuset. In this case, disable the local partition directly.
+	 * Otherwise, local_partition_update() will handle the automatic
+	 * transition between valid and invalid partition states.
 	 */
-	if (is_local_partition(cs) &&
-	    (!is_partition_valid(parent) ||
-	     tasks_nocpu_error(parent, cs, &new_cpus) ||
-	     cpumask_empty(subpartitions_cpus)))
-		partcmd = partcmd_invalidate;
+	if (is_local_partition(cs) && cpumask_empty(subpartitions_cpus))
+		partition_disable(cs, -cs->partition_root_state, PERR_HOTPLUG,
+				  tmp);
+	else
+		local_partition_update(cs, tmp);
+
 	/*
-	 * On the other hand, an invalid partition root may be transitioned
-	 * back to a regular one with a non-empty effective xcpus.
+	 * Recompute effective CPU mask after partition state update:
+	 * - For valid partitions: calculate partition-specific effective CPUs
+	 * - For invalid partitions: compute member effective CPU mask
 	 */
-	else if (is_partition_valid(parent) && is_partition_invalid(cs) &&
-		 !cpumask_empty(cs->effective_xcpus))
-		partcmd = partcmd_update;
-
-	if (partcmd >= 0) {
-		update_parent_effective_cpumask(cs, partcmd, NULL, tmp);
-		if (is_partition_valid(cs) || (partcmd == partcmd_invalidate)) {
-			compute_effective_cpumask(&new_cpus, cs, parent);
-			cpuset_force_rebuild();
-		}
-	}
+	compute_effective_cpumask(&new_cpus, cs, parent);
 
 update_tasks:
-	cpus_updated = !cpumask_equal(&new_cpus, cs->effective_cpus);
+	cpus_updated = !cpumask_equal(&new_cpus, &old_cpus);
 	mems_updated = !nodes_equal(new_mems, cs->effective_mems);
 	if (!cpus_updated && !mems_updated)
 		goto unlock;	/* Hotplug doesn't affect this cpuset */
-- 
2.34.1


  parent reply	other threads:[~2026-11-14  8:32 UTC|newest]

Thread overview: 13+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-14  8:31 [PATCH v5 00/12] cpuset: rework local partition logic Ridong Chen
2026-11-14  8:31 ` [PATCH v5 01/12] cpuset: introduce partition_enable() Ridong Chen
2026-11-14  8:31 ` [PATCH v5 02/12] cpuset: introduce partition_disable() Ridong Chen
2026-11-14  8:31 ` [PATCH v5 03/12] cpuset: introduce partition_update() Ridong Chen
2026-11-14  8:31 ` [PATCH v5 04/12] cpuset: use partition_enable() for remote partition enablement Ridong Chen
2026-11-14  8:31 ` [PATCH v5 05/12] cpuset: use partition_disable() for remote partition disablement Ridong Chen
2026-11-14  8:31 ` [PATCH v5 06/12] cpuset: use partition_update() for remote partition update Ridong Chen
2026-11-14  8:31 ` [PATCH v5 07/12] cpuset: introduce validate_local_partition() Ridong Chen
2026-11-14  8:31 ` [PATCH v5 08/12] cpuset: introduce local_partition_enable() Ridong Chen
2026-11-14  8:31 ` [PATCH v5 09/12] cpuset: use partition_disable() for local partition disablement Ridong Chen
2026-11-14  8:31 ` [PATCH v5 10/12] cpuset: use partition_disable() to invalidate local partition Ridong Chen
2026-11-14  8:31 ` Ridong Chen [this message]
2026-11-14  8:31 ` [PATCH v5 12/12] cpuset: remove update_parent_effective_cpumask Ridong Chen

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261114083124.1340140-12-ridong.chen@linux.dev \
    --to=ridong.chen@linux.dev \
    --cc=cgroups@vger.kernel.org \
    --cc=chenridong@xiaomi.com \
    --cc=hannes@cmpxchg.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=longman@redhat.com \
    --cc=mkoutny@suse.com \
    --cc=tj@kernel.org \
    --cc=zhangguopeng@kylinos.cn \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.