From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C5FC33EB0E0 for ; Thu, 25 Jun 2026 16:41:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1782405699; cv=none; b=gPy7pcVJOFNdLmBFSQqYIkNa1+GF/pcJ/LvC/gh8E09NspNdWE0p9a220Xm0/JDm0xrpDCHF2XjfQ1wiaqq24/q2a5Nsj6h5vu5oYRP9nfSfS8W+tqpYkD0lrdvQXNVbZyZDo5PPBJq02kw5PvBi7NpUyvOUovLAtI8EY62eGBg= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1782405699; c=relaxed/simple; bh=2+nPpX6n/eZlCvJ48U0II4nNqUaEAgNjajWsUbs4d2I=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=r6BPgA36Fl/DfW0+b69/lg97OgvPG/ot4JxzxlK39t0T38kBNLCse/oPnHzTcxwaLhL1sm9ytFoEMNxKzr0vgPpnh9RRCfeZjn5r/N26XrBcXzmmphhCnlklDi0mHmomOLJZ0AWlZvURjfeJgaNPaeYYE8sei+58Kqw78rKElXQ= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=c6DgjWp8; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="c6DgjWp8" Received: by smtp.kernel.org (Postfix) with ESMTPSA id CE3981F00A3A; Thu, 25 Jun 2026 16:41:35 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1782405697; bh=BV+rDekJMxwxSo6WNvOrM8cVZszf9Q1ttjjInW4D/Ek=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=c6DgjWp8D3SjHQVBVzaYJVCqXYOhrXBOXvfTpdlf9f28WSw28Bpehx86q5M2okq6y DVBzre7uAwAitrdOyfm/E7wb19ZaMYnhFUdqgU+zlfzc9JRfBR9xV77+kPamfyM9Sk YtAPBqcAxZZZWa+GOEA3wsjxoswLwRk1GupvumJdcCZIUvZ45ATMmV7/pUFmMIx9cg C9XsqwfPrejatEo6b8p8KtTDwjx2RKrjcAqEGMtAIGLlWaTKtDbCQCPVtauqNiItWQ M6kYRgZjBejQXhaGNFeml12gNzEBTcUvCcCBQqB3I96tWNJlzmhVpEOmRXS2mWbbC8 t2eFwQilm3Zlg== From: Frederic Weisbecker To: Christian Loehle Cc: LKML , Frederic Weisbecker , Anna-Maria Behnsen , Sehee Jeong , Thomas Gleixner , Peter Zijlstra Subject: [PATCH 5/6] timers/migration: Prefer lower capacity groups as migrators Date: Thu, 25 Jun 2026 18:41:13 +0200 Message-ID: <20260625164114.51454-6-frederic@kernel.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260625164114.51454-1-frederic@kernel.org> References: <20260625164114.51454-1-frederic@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Pulling timers to low capacity CPUs may improve power consumption by executing non performance critical ground work there and increasing the chances to turn the small CPUs into idle global migrators if such work keeps them alive for long enough. This way low power CPUs may be woken up from deep idle instead of high power CPUs. To implement this, migrators going idle will select the lowest alive capacity groups as new migrators. And CPUs exiting idle will force select themselves as the new migrator if their capacity is lower than the current migrator. Signed-off-by: Frederic Weisbecker --- include/linux/sched/topology.h | 1 + kernel/sched/topology.c | 20 ++++++++++++++++++++ kernel/time/timer_migration.c | 33 +++++++++++++++++++++++++++------ kernel/time/timer_migration.h | 2 ++ scripts/timer_migration_tree.py | 24 ++++++++++++------------ 5 files changed, 62 insertions(+), 18 deletions(-) diff --git a/include/linux/sched/topology.h b/include/linux/sched/topology.h index 88632825136e..de584bde7dee 100644 --- a/include/linux/sched/topology.h +++ b/include/linux/sched/topology.h @@ -52,6 +52,7 @@ extern const struct cpumask *tl_pkg_mask(struct sched_domain_topology_level *tl, extern int arch_asym_cpu_priority(int cpu); extern int sched_asym_count(void); extern int sched_asym_max_cpus(void); +extern int sched_asym_capacity_rank(unsigned long capacity); struct sched_domain_attr { int relax_domain_level; diff --git a/kernel/sched/topology.c b/kernel/sched/topology.c index 3b3bd32aea40..7673dfd579db 100644 --- a/kernel/sched/topology.c +++ b/kernel/sched/topology.c @@ -1760,6 +1760,26 @@ int sched_asym_max_cpus(void) return asym_capacity_max_cpus; } +int sched_asym_capacity_rank(unsigned long capacity) +{ + struct asym_cap_data *entry; + int i = 0; + + /* + * Search if capacity already exits. If not, track which the entry + * where we should insert to keep the list ordered descending. + */ + list_for_each_entry_reverse(entry, &asym_cap_list, link) { + if (capacity == entry->capacity) + return i; + i++; + } + + WARN_ONCE(1, "Capacity %lu not found in capacity list", capacity); + + return 0; +} + /* * Build-up/update list of CPUs grouped by their capacities * An update requires explicit request to rebuild sched domains diff --git a/kernel/time/timer_migration.c b/kernel/time/timer_migration.c index 2c2925046f43..a16d265df33e 100644 --- a/kernel/time/timer_migration.c +++ b/kernel/time/timer_migration.c @@ -677,11 +677,14 @@ static bool tmigr_active_up(struct tmigr_group *group, newstate = curstate; walk_done = true; - if (newstate.migrator == TMIGR_NONE) { + if (curstate.migrator == TMIGR_NONE || + (group->want_low_migrator && childmask < curstate.migrator)) { newstate.migrator = childmask; - /* Changes need to be propagated */ - walk_done = false; + if (curstate.migrator == TMIGR_NONE) { + /* Changes need to be propagated */ + walk_done = false; + } } newstate.active |= childmask; @@ -1644,6 +1647,12 @@ static void tmigr_init_group(struct tmigr_group *group, unsigned int lvl, group->num_children = 0; + /* Always prefer a migrator with lower capacity */ + if (sched_asym_count() > 1 && lvl == tmigr_crossfamily_level) + group->want_low_migrator = true; + else + group->want_low_migrator = false; + s.migrator = TMIGR_NONE; s.active = 0; s.seq = 0; @@ -1708,6 +1717,18 @@ static struct tmigr_group *tmigr_get_group(int family, unsigned int lvl) return group; } +static void tmigr_init_groupmask(struct tmigr_group *group, u8 groupmask) +{ + /* + * Overwrite the groupmask if this is a whole capacity group so that + * candidate migrators are sorted by capacity. + */ + if (sched_asym_count() > 1 && group->level == tmigr_crossfamily_level - 1) + groupmask = BIT(sched_asym_capacity_rank(group->family)); + + group->groupmask = groupmask; +} + static bool tmigr_init_root(struct tmigr_group *group, bool root_up) { if (!group->parent && group != tmigr_root) { @@ -1716,7 +1737,7 @@ static bool tmigr_init_root(struct tmigr_group *group, bool root_up) * to avoid accidents where yet another new top-level is * created in the future and made visible before this groupmask. */ - group->groupmask = BIT(0); + tmigr_init_groupmask(group, BIT(0)); WARN_ON_ONCE(root_up); return true; @@ -1750,10 +1771,10 @@ static void tmigr_connect_child_parent(struct tmigr_group *child, * to the CPU going up has been accounted as the second child. */ WARN_ON_ONCE(parent->num_children != 2); - child->groupmask = BIT(0); + tmigr_init_groupmask(child, BIT(0)); } else { /* Common case adding @child for the CPU going up to @parent. */ - child->groupmask = BIT(parent->num_children++); + tmigr_init_groupmask(child, BIT(parent->num_children++)); } /* diff --git a/kernel/time/timer_migration.h b/kernel/time/timer_migration.h index 3f6c7a110e3c..0bf3a0e7d54c 100644 --- a/kernel/time/timer_migration.h +++ b/kernel/time/timer_migration.h @@ -58,6 +58,7 @@ struct tmigr_event { * tmigr_level_list; is required during setup when a * new group needs to be connected to the existing * hierarchy groups + * @want_low_migrator: Group wants the lowest capacity migrator */ struct tmigr_group { raw_spinlock_t lock; @@ -71,6 +72,7 @@ struct tmigr_group { unsigned int num_children; u8 groupmask; struct list_head list; + bool want_low_migrator; }; /** diff --git a/scripts/timer_migration_tree.py b/scripts/timer_migration_tree.py index abb321b903c4..4f055fc08435 100755 --- a/scripts/timer_migration_tree.py +++ b/scripts/timer_migration_tree.py @@ -33,8 +33,8 @@ class Node: def set_lvl(self, lvl): self.lvl = lvl - def set_numa(self, numa): - self.numa = numa + def set_family(self, family): + self.family = family def set_num_children(self, num_children): self.num_children = num_children @@ -44,7 +44,7 @@ class Node: parent_grp = self.parent.group else: parent_grp = "-" - return "Group: %s mask: %s parent: %s lvl: %d numa: %d num_children: %d" % (self.group, self.groupmask, parent_grp, self.lvl, self.numa, self.num_children) + return "Group: %s mask: %s parent: %s lvl: %d family: %d num_children: %d" % (self.group, self.groupmask, parent_grp, self.lvl, self.family, self.num_children) def get_node(group): if group in Node.node_list: @@ -55,32 +55,32 @@ def get_node(group): return n def tmigr_connect_cpu_parent(ts, line): - s = re.search("tmigr_connect_cpu_parent: cpu=([0-9]+) groupmask=([0-9a-zA-Z]+) parent=([0-9a-zA-Z]+) lvl=([0-9]+) numa=([-]?[0-9]+) num_children=([0-9]+)", line) + s = re.search("tmigr_connect_cpu_parent: cpu=([0-9]+) groupmask=([0-9a-zA-Z]+) parent=([0-9a-zA-Z]+) lvl=([0-9]+) family=([-]?[0-9]+) num_children=([0-9]+)", line) if s is None: return False - (cpu, groupmask, parent, lvl, numa, num_children) = (int(s.group(1)), s.group(2), s.group(3), int(s.group(4)), int(s.group(5)), int(s.group(6))) + (cpu, groupmask, parent, lvl, family, num_children) = (int(s.group(1)), s.group(2), s.group(3), int(s.group(4)), int(s.group(5)), int(s.group(6))) n = get_node(cpu) p = get_node(parent) n.set_parent(p) n.set_groupmask(groupmask) n.set_lvl(-1) p.set_lvl(lvl) - p.set_numa(numa) - n.set_numa(numa) + p.set_family(family) + n.set_family(family) p.set_num_children(num_children) p.add_child(n) def tmigr_connect_child_parent(ts, line): - s = re.search("tmigr_connect_child_parent: group=([0-9a-zA-Z]+) groupmask=([0-9a-zA-Z]+) parent=([0-9a-zA-Z]+) lvl=([0-9]+) numa=([-]?[0-9]+) num_children=([0-9]+)", line) + s = re.search("tmigr_connect_child_parent: group=([0-9a-zA-Z]+) groupmask=([0-9a-zA-Z]+) parent=([0-9a-zA-Z]+) lvl=([0-9]+) family=([-]?[0-9]+) num_children=([0-9]+)", line) if s is None: return False - (group, groupmask, parent, lvl, numa, num_children) = (s.group(1), s.group(2), s.group(3), int(s.group(4)), int(s.group(5)), int(s.group(6))) + (group, groupmask, parent, lvl, family, num_children) = (s.group(1), s.group(2), s.group(3), int(s.group(4)), int(s.group(5)), int(s.group(6))) n = get_node(group) p = get_node(parent) n.set_parent(p) n.set_groupmask(groupmask) p.set_lvl(lvl) - p.set_numa(numa) + p.set_family(family) p.set_num_children(num_children) p.add_child(n) @@ -88,7 +88,7 @@ def populate(enode, node): enode = enode.add_child(name = node.group) enode.add_feature("groupmask", "m:%s" % node.groupmask) enode.add_feature("lvl", "lvl:%d" % node.lvl) - enode.add_feature("numa", "node %d" % node.numa) + enode.add_feature("family", "family %d" % node.family) enode.add_feature("num_children", "c=%d" % node.num_children) for child in node.children: populate(enode, child) @@ -107,4 +107,4 @@ if __name__ == "__main__": group = group.parent root = Tree() populate(root, group) - print(root.get_ascii(show_internal=True, attributes=["name", "numa", "lvl"])) + print(root.get_ascii(show_internal=True, attributes=["name", "family", "lvl"])) -- 2.53.0