xref: /linux/kernel/sched/ext/sub.c (revision f883dbb64ca53f75d9006d1e73180c9d9ecfc9a2)
1daf8e166STejun Heo // SPDX-License-Identifier: GPL-2.0
2daf8e166STejun Heo /*
3daf8e166STejun Heo  * BPF extensible scheduler class: Documentation/scheduler/sched-ext.rst
4daf8e166STejun Heo  *
5daf8e166STejun Heo  * Sub-scheduler hierarchy support.
6daf8e166STejun Heo  *
7daf8e166STejun Heo  * A sub-scheduler is an scx_sched attached to a cgroup subtree under another
8daf8e166STejun Heo  * scx_sched. This file holds the sub-scheduler implementation: the scheduler
9daf8e166STejun Heo  * tree walk, capability delegation, per-shard cap state and its sync, and the
10daf8e166STejun Heo  * sub-scheduler enable/disable paths. The core dispatch/enqueue machinery it
11daf8e166STejun Heo  * builds on lives in ext.c.
12daf8e166STejun Heo  *
13daf8e166STejun Heo  * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
14daf8e166STejun Heo  * Copyright (c) 2026 Tejun Heo <tj@kernel.org>
15daf8e166STejun Heo  */
16daf8e166STejun Heo #include <linux/rhashtable.h>
17daf8e166STejun Heo #include "internal.h"
18daf8e166STejun Heo #include "cid.h"
19daf8e166STejun Heo #include "arena.h"
20daf8e166STejun Heo #include "sub.h"
217f480f34STejun Heo #include "inlines.h"
22daf8e166STejun Heo 
23daf8e166STejun Heo #ifdef CONFIG_EXT_SUB_SCHED
24daf8e166STejun Heo 
258946dbd3STejun Heo /*
268946dbd3STejun Heo  * On while any sub-scheduler exists so that a root-only system doesn't pay for
278946dbd3STejun Heo  * the sub-sched portions of hot paths. See scx_has_subs().
288946dbd3STejun Heo  */
298946dbd3STejun Heo DEFINE_STATIC_KEY_FALSE(__scx_has_subs);
308946dbd3STejun Heo 
31daf8e166STejun Heo /**
32bbda59d8STejun Heo  * scx_skip_subtree_pre - Skip @pos's subtree in a pre-order walk
33bbda59d8STejun Heo  * @pos: current position
34bbda59d8STejun Heo  * @root: walk root
35bbda59d8STejun Heo  *
36bbda59d8STejun Heo  * In a walk started by scx_next_descendant_pre(), continue past @pos's subtree:
37bbda59d8STejun Heo  * return @pos's next sibling, or the closest ancestor's next sibling, or NULL
38bbda59d8STejun Heo  * if @pos's subtree is the last under @root. Same locking rules.
39bbda59d8STejun Heo  */
40bbda59d8STejun Heo struct scx_sched *scx_skip_subtree_pre(struct scx_sched *pos, struct scx_sched *root)
41bbda59d8STejun Heo {
42bbda59d8STejun Heo 	struct scx_sched *next;
43bbda59d8STejun Heo 
44bbda59d8STejun Heo 	lockdep_assert(lockdep_is_held(&scx_enable_mutex) ||
45bbda59d8STejun Heo 		       lockdep_is_held(&scx_sched_lock) ||
46bbda59d8STejun Heo 		       rcu_read_lock_any_held());
47bbda59d8STejun Heo 
48bbda59d8STejun Heo 	while (pos != root) {
49bbda59d8STejun Heo 		next = list_next_or_null_rcu(&scx_parent(pos)->children, &pos->sibling,
50bbda59d8STejun Heo 					     struct scx_sched, sibling);
51bbda59d8STejun Heo 		if (next)
52bbda59d8STejun Heo 			return next;
53bbda59d8STejun Heo 		pos = scx_parent(pos);
54bbda59d8STejun Heo 	}
55bbda59d8STejun Heo 	return NULL;
56bbda59d8STejun Heo }
57bbda59d8STejun Heo 
58bbda59d8STejun Heo /**
59daf8e166STejun Heo  * scx_next_descendant_pre - find the next descendant for pre-order walk
60daf8e166STejun Heo  * @pos: the current position (%NULL to initiate traversal)
61daf8e166STejun Heo  * @root: sched whose descendants to walk
62daf8e166STejun Heo  *
63daf8e166STejun Heo  * To be used by scx_for_each_descendant_pre(). Find the next descendant to
64daf8e166STejun Heo  * visit for pre-order traversal of @root's descendants. @root is included in
65daf8e166STejun Heo  * the iteration and the first node to be visited.
66daf8e166STejun Heo  */
67daf8e166STejun Heo struct scx_sched *scx_next_descendant_pre(struct scx_sched *pos, struct scx_sched *root)
68daf8e166STejun Heo {
69daf8e166STejun Heo 	struct scx_sched *next;
70daf8e166STejun Heo 
71daf8e166STejun Heo 	lockdep_assert(lockdep_is_held(&scx_enable_mutex) ||
7270f8b178STejun Heo 		       lockdep_is_held(&scx_sched_lock) ||
7370f8b178STejun Heo 		       rcu_read_lock_any_held());
74daf8e166STejun Heo 
75daf8e166STejun Heo 	/* if first iteration, visit @root */
76daf8e166STejun Heo 	if (!pos)
77daf8e166STejun Heo 		return root;
78daf8e166STejun Heo 
79daf8e166STejun Heo 	/* visit the first child if exists */
8070f8b178STejun Heo 	next = list_first_or_null_rcu(&pos->children, struct scx_sched, sibling);
81daf8e166STejun Heo 	if (next)
82daf8e166STejun Heo 		return next;
83daf8e166STejun Heo 
84daf8e166STejun Heo 	/* no child, visit my or the closest ancestor's next sibling */
85bbda59d8STejun Heo 	return scx_skip_subtree_pre(pos, root);
86daf8e166STejun Heo }
87daf8e166STejun Heo 
88daf8e166STejun Heo static struct scx_sched *scx_find_sub_sched(u64 cgroup_id)
89daf8e166STejun Heo {
90daf8e166STejun Heo 	return rhashtable_lookup(&scx_sched_hash, &cgroup_id,
91daf8e166STejun Heo 				 scx_sched_hash_params);
92daf8e166STejun Heo }
93daf8e166STejun Heo 
94daf8e166STejun Heo void scx_set_task_sched(struct task_struct *p, struct scx_sched *sch)
95daf8e166STejun Heo {
96daf8e166STejun Heo 	rcu_assign_pointer(p->scx.sched, sch);
97daf8e166STejun Heo }
98daf8e166STejun Heo 
99daf8e166STejun Heo struct cgroup *sch_cgroup(struct scx_sched *sch)
100daf8e166STejun Heo {
101daf8e166STejun Heo 	return sch->cgrp;
102daf8e166STejun Heo }
103daf8e166STejun Heo 
104daf8e166STejun Heo /* for each descendant of @cgrp including self, set ->scx_sched to @sch */
105daf8e166STejun Heo void set_cgroup_sched(struct cgroup *cgrp, struct scx_sched *sch)
106daf8e166STejun Heo {
107daf8e166STejun Heo 	struct cgroup *pos;
108daf8e166STejun Heo 	struct cgroup_subsys_state *css;
109daf8e166STejun Heo 
110daf8e166STejun Heo 	cgroup_for_each_live_descendant_pre(pos, css, cgrp)
111daf8e166STejun Heo 		rcu_assign_pointer(pos->scx_sched, sch);
112daf8e166STejun Heo }
113daf8e166STejun Heo 
1148dba3bbdSTejun Heo static void free_pshard(struct scx_pshard *pshard)
1158dba3bbdSTejun Heo {
1165f2a9a4cSTejun Heo 	struct scx_caps_updated *cu;
1175f2a9a4cSTejun Heo 
1185f2a9a4cSTejun Heo 	if (!pshard)
1195f2a9a4cSTejun Heo 		return;
1205f2a9a4cSTejun Heo 	cu = &pshard->caps_updated;
1215f2a9a4cSTejun Heo 	if (cu->cmask_arena_out)
1225f2a9a4cSTejun Heo 		scx_arena_free(pshard->sch, cu->cmask_arena_out,
1235f2a9a4cSTejun Heo 			       struct_size_t(struct scx_cmask, bits,
1245f2a9a4cSTejun Heo 					     SCX_CMASK_NR_WORDS(pshard->nr_cids)));
1258dba3bbdSTejun Heo 	kfree(pshard);
1268dba3bbdSTejun Heo }
1278dba3bbdSTejun Heo 
1288dba3bbdSTejun Heo void scx_free_pshards(struct scx_sched *sch)
1298dba3bbdSTejun Heo {
1308dba3bbdSTejun Heo 	s32 si;
1318dba3bbdSTejun Heo 
1328dba3bbdSTejun Heo 	if (!sch->pshard)
1338dba3bbdSTejun Heo 		return;
1348dba3bbdSTejun Heo 	for (si = 0; si < sch->nr_pshards; si++)
1358dba3bbdSTejun Heo 		free_pshard(sch->pshard[si]);
1368dba3bbdSTejun Heo 	kfree(sch->pshard);
1378dba3bbdSTejun Heo }
1388dba3bbdSTejun Heo 
1398dba3bbdSTejun Heo static struct scx_pshard *alloc_pshard(struct scx_sched *sch, s32 shard_idx, s32 node)
1408dba3bbdSTejun Heo {
1413a773220STejun Heo 	const struct scx_cid_shard *shard =
1423a773220STejun Heo 		&rcu_dereference_protected(scx_cid_shard_ranges,
1433a773220STejun Heo 					   lockdep_is_held(&scx_enable_mutex))[shard_idx];
1445f2a9a4cSTejun Heo 	size_t cmask_size = struct_size_t(struct scx_cmask, bits,
1455f2a9a4cSTejun Heo 					  SCX_CMASK_NR_WORDS(shard->nr_cids));
14686094b95STejun Heo 	struct scx_pshard *pshard;
1475f2a9a4cSTejun Heo 	struct scx_caps_updated *cu;
14886094b95STejun Heo 	s32 i;
14986094b95STejun Heo 
15086094b95STejun Heo 	pshard = kzalloc_node(sizeof(*pshard), GFP_KERNEL, node);
15186094b95STejun Heo 	if (!pshard)
15286094b95STejun Heo 		return NULL;
15386094b95STejun Heo 
15486094b95STejun Heo 	raw_spin_lock_init(&pshard->lock);
15586094b95STejun Heo 	pshard->sch = sch;
1565f2a9a4cSTejun Heo 	pshard->base = shard->base_cid;
1575f2a9a4cSTejun Heo 	pshard->nr_cids = shard->nr_cids;
15886094b95STejun Heo 
15986094b95STejun Heo 	for (i = 0; i < __SCX_NR_CAPS; i++)
16086094b95STejun Heo 		scx_cmask_init(&pshard->caps[i].cmask, shard->base_cid, shard->nr_cids);
16186094b95STejun Heo 
1625f2a9a4cSTejun Heo 	cu = &pshard->caps_updated;
1635f2a9a4cSTejun Heo 	raw_spin_lock_init(&cu->lock);
1645f2a9a4cSTejun Heo 	INIT_LIST_HEAD(&cu->node_in_flight);
1655f2a9a4cSTejun Heo 	__scx_cmask_init(&cu->cmask, shard->base_cid, shard->nr_cids, SCX_CID_SHARD_MAX_CPUS);
1665f2a9a4cSTejun Heo 
1675f2a9a4cSTejun Heo 	cu->cmask_arena_out = scx_arena_alloc(sch, cmask_size);
1685f2a9a4cSTejun Heo 	if (!cu->cmask_arena_out) {
1695f2a9a4cSTejun Heo 		free_pshard(pshard);
1705f2a9a4cSTejun Heo 		return NULL;
1715f2a9a4cSTejun Heo 	}
1725f2a9a4cSTejun Heo 
1735f2a9a4cSTejun Heo 	scx_cmask_init(cu->cmask_arena_out, shard->base_cid, shard->nr_cids);
1745f2a9a4cSTejun Heo 
17586094b95STejun Heo 	return pshard;
1768dba3bbdSTejun Heo }
1778dba3bbdSTejun Heo 
1788dba3bbdSTejun Heo s32 scx_alloc_pshards(struct scx_sched *sch)
1798dba3bbdSTejun Heo {
1808dba3bbdSTejun Heo 	struct scx_pshard **pshard;
1813a773220STejun Heo 	s32 *shard_node;
1828dba3bbdSTejun Heo 	s32 si;
1838dba3bbdSTejun Heo 
1848dba3bbdSTejun Heo 	if (!sch->is_cid_type || !sch->arena_pool)
1858dba3bbdSTejun Heo 		return 0;
1868dba3bbdSTejun Heo 
1873a773220STejun Heo 	shard_node = rcu_dereference_protected(scx_shard_node,
1883a773220STejun Heo 					       lockdep_is_held(&scx_enable_mutex));
1893a773220STejun Heo 
1908dba3bbdSTejun Heo 	pshard = kzalloc_objs(pshard[0], scx_nr_cid_shards, GFP_KERNEL);
1918dba3bbdSTejun Heo 	if (!pshard)
1928dba3bbdSTejun Heo 		return -ENOMEM;
1938dba3bbdSTejun Heo 
1948dba3bbdSTejun Heo 	for (si = 0; si < scx_nr_cid_shards; si++) {
1953a773220STejun Heo 		pshard[si] = alloc_pshard(sch, si, shard_node[si]);
1968dba3bbdSTejun Heo 		if (!pshard[si]) {
1978dba3bbdSTejun Heo 			while (--si >= 0)
1988dba3bbdSTejun Heo 				free_pshard(pshard[si]);
1998dba3bbdSTejun Heo 			kfree(pshard);
2008dba3bbdSTejun Heo 			return -ENOMEM;
2018dba3bbdSTejun Heo 		}
2028dba3bbdSTejun Heo 	}
2038dba3bbdSTejun Heo 
2048dba3bbdSTejun Heo 	sch->nr_pshards = scx_nr_cid_shards;
2058dba3bbdSTejun Heo 	/*
2068dba3bbdSTejun Heo 	 * Publish only after every entry is built so a reader observing
2073a773220STejun Heo 	 * @sch->pshard never sees a partially-filled array or unpublished cid
2083a773220STejun Heo 	 * tables. Pair the store with a barrier and an acquire load on the
2093a773220STejun Heo 	 * read side.
2108dba3bbdSTejun Heo 	 */
2118dba3bbdSTejun Heo 	smp_wmb();
2128dba3bbdSTejun Heo 	WRITE_ONCE(sch->pshard, pshard);
2138dba3bbdSTejun Heo 	return 0;
2148dba3bbdSTejun Heo }
2158dba3bbdSTejun Heo 
21686094b95STejun Heo /*
21786094b95STejun Heo  * Seed the root's caps fully. Root owns all cids on all caps at enable time.
21886094b95STejun Heo  * Children acquire caps via scx_bpf_sub_grant().
21986094b95STejun Heo  */
22086094b95STejun Heo void scx_init_root_caps(struct scx_sched *sch)
22186094b95STejun Heo {
22286094b95STejun Heo 	s32 si, i;
22386094b95STejun Heo 
22486094b95STejun Heo 	for (si = 0; si < sch->nr_pshards; si++) {
22586094b95STejun Heo 		struct scx_pshard *ps = sch->pshard[si];
22686094b95STejun Heo 
22786094b95STejun Heo 		for (i = 0; i < __SCX_NR_CAPS; i++)
22886094b95STejun Heo 			scx_cmask_fill(&ps->caps[i].cmask);
22986094b95STejun Heo 	}
23086094b95STejun Heo }
23186094b95STejun Heo 
23275a8c820STejun Heo /**
23375a8c820STejun Heo  * scx_local_or_reject_dsq - Pick the local or reject DSQ for an insert
23475a8c820STejun Heo  * @sch: enqueuing sub-sched
23575a8c820STejun Heo  * @rq: rq whose local DSQ @p targets
23675a8c820STejun Heo  * @p: task being inserted
2376ea3be36STejun Heo  * @enq_flags: in/out, unhonored flags are cleared
23875a8c820STejun Heo  *
23975a8c820STejun Heo  * Return @rq's local DSQ if @sch holds the required caps on @rq's cid,
24075a8c820STejun Heo  * otherwise @rq's reject DSQ after recording the reenq reason on @p.
24175a8c820STejun Heo  *
2426ea3be36STejun Heo  * %SCX_ENQ_IMMED and %SCX_ENQ_PREEMPT are cleared when diverting to reject.
2436ea3be36STejun Heo  * %SCX_ENQ_PREEMPT is also cleared on a fallback migration-disabled admission.
2446ea3be36STejun Heo  *
24575a8c820STejun Heo  * Bypass doesn't need special-casing as a bypassing sched's tasks are enqueued
24675a8c820STejun Heo  * to and run by its nearest non-bypassing ancestor. If root is bypassing, it
24775a8c820STejun Heo  * always holds all caps.
24875a8c820STejun Heo  */
24975a8c820STejun Heo struct scx_dispatch_q *scx_local_or_reject_dsq(struct scx_sched *sch, struct rq *rq,
25075a8c820STejun Heo 					       struct task_struct *p, u64 *enq_flags)
25175a8c820STejun Heo {
2528946dbd3STejun Heo 	if (!scx_has_subs())
2538946dbd3STejun Heo 		return &rq->scx.local_dsq;
2548946dbd3STejun Heo 
25575a8c820STejun Heo 	s32 cid = __scx_cpu_to_cid(cpu_of(rq));
256f2c9f515STejun Heo 	struct scx_sched *asch = rq->scx.remote_activate_sch ?: sch;
2576ea3be36STejun Heo 	u64 needed = scx_caps_for_enq(*enq_flags);
2586ea3be36STejun Heo 	u64 missing;
2596ea3be36STejun Heo 
260f2c9f515STejun Heo 	/*
261f2c9f515STejun Heo 	 * On a remote activation the scheduling sched (@asch) differs from
262f2c9f515STejun Heo 	 * @p's owner (@sch). Check caps against the scheduling sched.
263f2c9f515STejun Heo 	 */
2646ea3be36STejun Heo 	if (*enq_flags & SCX_ENQ_PREEMPT)
265f2c9f515STejun Heo 		needed |= scx_caps_for_preempt(asch, rq);
266f2c9f515STejun Heo 	missing = scx_missing_caps(asch, cpu_of(rq), needed);
26775a8c820STejun Heo 
26875a8c820STejun Heo 	/* requirements met */
26975a8c820STejun Heo 	if (likely(!missing))
27075a8c820STejun Heo 		return &rq->scx.local_dsq;
27175a8c820STejun Heo 
27275a8c820STejun Heo 	/*
27375a8c820STejun Heo 	 * The task must run on this CPU regardless of caps: the rq is draining
27475a8c820STejun Heo 	 * offline (BPF scheduler bypassed), the task is migration-disabled, or a
27575a8c820STejun Heo 	 * migration is pending. Admit despite the missing caps and count it.
2766ea3be36STejun Heo 	 * Refuse preemptions.
27775a8c820STejun Heo 	 */
27875a8c820STejun Heo 	if (unlikely(!scx_rq_online(rq) || is_migration_disabled(p) ||
27975a8c820STejun Heo 		     p->migration_pending)) {
28075a8c820STejun Heo 		__scx_add_event(sch, SCX_EV_SUB_FORCED_ADMIT, 1);
2816ea3be36STejun Heo 		*enq_flags &= ~SCX_ENQ_PREEMPT;
28275a8c820STejun Heo 		return &rq->scx.local_dsq;
28375a8c820STejun Heo 	}
28475a8c820STejun Heo 
28575a8c820STejun Heo 	p->scx.reenq_reason_caps = missing;
28675a8c820STejun Heo 	p->scx.reenq_reason_cid = cid;
28775a8c820STejun Heo 
28875a8c820STejun Heo 	/*
28975a8c820STejun Heo 	 * Only local DSQ can honor IMMED and dsq_inc_nr() WARNs on IMMED into
29075a8c820STejun Heo 	 * others. Strip both the enq flag and the sticky task flag - the
2916ea3be36STejun Heo 	 * latter can carry in from an earlier admitted IMMED insert. Strip
2926ea3be36STejun Heo 	 * PREEMPT too.
29375a8c820STejun Heo 	 */
2946ea3be36STejun Heo 	*enq_flags &= ~(SCX_ENQ_IMMED | SCX_ENQ_PREEMPT);
29575a8c820STejun Heo 	p->scx.flags &= ~SCX_TASK_IMMED;
29675a8c820STejun Heo 
29775a8c820STejun Heo 	return &rq->scx.reject_dsq;
29875a8c820STejun Heo }
29975a8c820STejun Heo 
30075a8c820STejun Heo /* @p lost the caps needed to stay on @rq's local DSQ? Record reason if so. */
30175a8c820STejun Heo bool scx_task_reenq_on_cap_revoke(struct rq *rq, struct task_struct *p)
30275a8c820STejun Heo {
30375a8c820STejun Heo 	u64 missing;
30475a8c820STejun Heo 
30575a8c820STejun Heo 	/* migration-disabled tasks are admitted regardless of caps */
30675a8c820STejun Heo 	if (is_migration_disabled(p))
30775a8c820STejun Heo 		return false;
30875a8c820STejun Heo 
30975a8c820STejun Heo 	missing = scx_missing_caps(scx_task_sched(p), cpu_of(rq), scx_caps_for_task(p));
31075a8c820STejun Heo 	if (likely(!missing))
31175a8c820STejun Heo 		return false;
31275a8c820STejun Heo 
31375a8c820STejun Heo 	p->scx.reenq_reason_caps = missing;
31475a8c820STejun Heo 	p->scx.reenq_reason_cid = __scx_cpu_to_cid(cpu_of(rq));
31575a8c820STejun Heo 	return true;
31675a8c820STejun Heo }
31775a8c820STejun Heo 
31875a8c820STejun Heo /*
31975a8c820STejun Heo  * Drain @rq->scx.reject_dsq, reenqueueing each task so the BPF re-decides
32075a8c820STejun Heo  * from p->scx.reenq_reason_*.
32175a8c820STejun Heo  *
3227706d6e4STejun Heo  * A task can be re-rejected repeatedly. The reenqueue is bounded per task in
3237706d6e4STejun Heo  * scx_do_enqueue_task(), which ejects the owning sub past SCX_REENQ_MAX_REPEAT.
3247706d6e4STejun Heo  * Rejection can't happen for root.
32575a8c820STejun Heo  */
32675a8c820STejun Heo void scx_reenq_reject(struct rq *rq)
32775a8c820STejun Heo {
32875a8c820STejun Heo 	LIST_HEAD(tasks);
32975a8c820STejun Heo 	struct task_struct *p, *n;
33075a8c820STejun Heo 
33175a8c820STejun Heo 	lockdep_assert_rq_held(rq);
33275a8c820STejun Heo 
3338946dbd3STejun Heo 	if (!scx_has_subs() || list_empty(&rq->scx.reject_dsq.list))
33475a8c820STejun Heo 		return;
33575a8c820STejun Heo 
33675a8c820STejun Heo 	/*
33775a8c820STejun Heo 	 * Move to a private list so a task re-rejected by the
33875a8c820STejun Heo 	 * scx_do_enqueue_task() below isn't revisited this round.
33975a8c820STejun Heo 	 */
34075a8c820STejun Heo 	list_for_each_entry_safe(p, n, &rq->scx.reject_dsq.list, scx.dsq_list.node) {
34175a8c820STejun Heo 		/* migration_pending tasks should have bypassed to local DSQ */
34275a8c820STejun Heo 		if (WARN_ON_ONCE(p->migration_pending))
34375a8c820STejun Heo 			continue;
34475a8c820STejun Heo 
34575a8c820STejun Heo 		scx_dispatch_dequeue(rq, p);
34675a8c820STejun Heo 
34775a8c820STejun Heo 		if (WARN_ON_ONCE(p->scx.flags & SCX_TASK_REENQ_REASON_MASK))
34875a8c820STejun Heo 			p->scx.flags &= ~SCX_TASK_REENQ_REASON_MASK;
34975a8c820STejun Heo 		p->scx.flags |= SCX_TASK_REENQ_CAP;
35075a8c820STejun Heo 
35175a8c820STejun Heo 		list_add_tail(&p->scx.dsq_list.node, &tasks);
35275a8c820STejun Heo 	}
35375a8c820STejun Heo 
35475a8c820STejun Heo 	list_for_each_entry_safe(p, n, &tasks, scx.dsq_list.node) {
35575a8c820STejun Heo 		list_del_init(&p->scx.dsq_list.node);
35675a8c820STejun Heo 
35775a8c820STejun Heo 		scx_do_enqueue_task(rq, p, SCX_ENQ_REENQ, -1);
35875a8c820STejun Heo 
35975a8c820STejun Heo 		p->scx.flags &= ~SCX_TASK_REENQ_REASON_MASK;
36075a8c820STejun Heo 	}
36175a8c820STejun Heo }
36275a8c820STejun Heo 
3635f2a9a4cSTejun Heo /* record a caps change, see struct scx_caps_updated */
3645f2a9a4cSTejun Heo static void caps_updated_record(struct scx_pshard *ps, const struct scx_cmask *cids, u64 caps,
3655f2a9a4cSTejun Heo 				struct list_head *to_deliver)
3665f2a9a4cSTejun Heo {
3675f2a9a4cSTejun Heo 	struct scx_caps_updated *cu = &ps->caps_updated;
3685f2a9a4cSTejun Heo 
3695f2a9a4cSTejun Heo 	guard(raw_spinlock)(&cu->lock);
3705f2a9a4cSTejun Heo 	scx_cmask_or(&cu->cmask, cids);
3715f2a9a4cSTejun Heo 	cu->caps |= caps;
3725f2a9a4cSTejun Heo 	if (list_empty(&cu->node_in_flight))
3735f2a9a4cSTejun Heo 		list_add_tail(&cu->node_in_flight, to_deliver);
3745f2a9a4cSTejun Heo }
3755f2a9a4cSTejun Heo 
3765f2a9a4cSTejun Heo /* deliver queued caps_updated callbacks, see struct scx_caps_updated */
3775f2a9a4cSTejun Heo static void caps_updated_deliver(struct list_head *to_deliver)
3785f2a9a4cSTejun Heo {
3795f2a9a4cSTejun Heo 	struct scx_caps_updated *cu, *tmp;
3805f2a9a4cSTejun Heo 
3815f2a9a4cSTejun Heo 	list_for_each_entry_safe(cu, tmp, to_deliver, node_in_flight) {
3825f2a9a4cSTejun Heo 		struct scx_pshard *ps = container_of(cu, struct scx_pshard, caps_updated);
3835f2a9a4cSTejun Heo 		struct scx_sched *sch = ps->sch;
3845f2a9a4cSTejun Heo 
3855f2a9a4cSTejun Heo 		while (true) {
3865f2a9a4cSTejun Heo 			u64 caps = 0;
3875f2a9a4cSTejun Heo 
3885f2a9a4cSTejun Heo 			/*
3895f2a9a4cSTejun Heo 			 * During enable, has_op is set after ops.sub_attach(),
3905f2a9a4cSTejun Heo 			 * so !has_op means the op is absent or the sched isn't
3915f2a9a4cSTejun Heo 			 * live yet - e.g. caps grant from ops.sub_attach().
3925f2a9a4cSTejun Heo 			 * Either way don't consume - leave for
3935f2a9a4cSTejun Heo 			 * scx_sub_seed_caps() to deliver once live.
3945f2a9a4cSTejun Heo 			 */
3955f2a9a4cSTejun Heo 			scoped_guard (raw_spinlock, &cu->lock) {
3965f2a9a4cSTejun Heo 				if (cu->caps && SCX_HAS_OP(sch, sub_caps_updated) &&
3975f2a9a4cSTejun Heo 				    likely(!READ_ONCE(sch->aborting))) {
3985f2a9a4cSTejun Heo 					struct scx_cmask_ref ref;
3995f2a9a4cSTejun Heo 
4005f2a9a4cSTejun Heo 					caps = cu->caps;
4015f2a9a4cSTejun Heo 					scx_cmask_ref_init_kern(sch, cu->cmask_arena_out,
4025f2a9a4cSTejun Heo 								ps->base, ps->nr_cids, &ref);
4035f2a9a4cSTejun Heo 					scx_cmask_ref_copy(&ref, &cu->cmask);
4045f2a9a4cSTejun Heo 					scx_cmask_clear(&cu->cmask);
4055f2a9a4cSTejun Heo 					cu->caps = 0;
4065f2a9a4cSTejun Heo 				} else {
4075f2a9a4cSTejun Heo 					list_del_init(&cu->node_in_flight);
4085f2a9a4cSTejun Heo 				}
4095f2a9a4cSTejun Heo 			}
4105f2a9a4cSTejun Heo 			if (!caps)
4115f2a9a4cSTejun Heo 				break;
4125f2a9a4cSTejun Heo 
4135f2a9a4cSTejun Heo 			/* caps != 0 only when deliverable (has_op, above) */
4145f2a9a4cSTejun Heo 			SCX_CALL_OP(sch, sub_caps_updated, NULL,
4155f2a9a4cSTejun Heo 				    scx_kaddr_to_arena(sch, cu->cmask_arena_out),
4165f2a9a4cSTejun Heo 				    caps);
4175f2a9a4cSTejun Heo 		}
4185f2a9a4cSTejun Heo 	}
4195f2a9a4cSTejun Heo }
4205f2a9a4cSTejun Heo 
4215f2a9a4cSTejun Heo /*
4225f2a9a4cSTejun Heo  * Deliver caps owed to @sch that couldn't be delivered earlier (e.g. a grant
4235f2a9a4cSTejun Heo  * taken during its sub_attach(), before has_op was set). Called once @sch is
4245f2a9a4cSTejun Heo  * enabled.
4255f2a9a4cSTejun Heo  */
4265f2a9a4cSTejun Heo static void scx_sub_seed_caps(struct scx_sched *sch)
4275f2a9a4cSTejun Heo {
4285f2a9a4cSTejun Heo 	LIST_HEAD(to_deliver);
4295f2a9a4cSTejun Heo 	s32 si;
4305f2a9a4cSTejun Heo 
4315f2a9a4cSTejun Heo 	guard(irqsave)();
4325f2a9a4cSTejun Heo 
4335f2a9a4cSTejun Heo 	for (si = 0; si < sch->nr_pshards; si++) {
4345f2a9a4cSTejun Heo 		struct scx_pshard *ps = sch->pshard[si];
4355f2a9a4cSTejun Heo 		struct scx_caps_updated *cu = &ps->caps_updated;
4365f2a9a4cSTejun Heo 
4375f2a9a4cSTejun Heo 		scoped_guard (raw_spinlock, &cu->lock) {
4385f2a9a4cSTejun Heo 			if (cu->caps && list_empty(&cu->node_in_flight))
4395f2a9a4cSTejun Heo 				list_add_tail(&cu->node_in_flight, &to_deliver);
4405f2a9a4cSTejun Heo 		}
4415f2a9a4cSTejun Heo 	}
4425f2a9a4cSTejun Heo 	caps_updated_deliver(&to_deliver);
4435f2a9a4cSTejun Heo }
4445f2a9a4cSTejun Heo 
44556fdc35bSTejun Heo static u64 calc_effective_caps(struct scx_pshard *ps, s32 cid)
44656fdc35bSTejun Heo {
44756fdc35bSTejun Heo 	u64 ecaps = 0;
44856fdc35bSTejun Heo 	u32 cap_bit;
44956fdc35bSTejun Heo 
45056fdc35bSTejun Heo 	for (cap_bit = 0; cap_bit < __SCX_NR_CAPS; cap_bit++)
45156fdc35bSTejun Heo 		if (scx_cmask_test(cid, &ps->caps[cap_bit].cmask))
45256fdc35bSTejun Heo 			ecaps |= BIT_U64(cap_bit) | scx_caps_implied(BIT_U64(cap_bit));
45356fdc35bSTejun Heo 	return ecaps;
45456fdc35bSTejun Heo }
45556fdc35bSTejun Heo 
45656fdc35bSTejun Heo /**
45756fdc35bSTejun Heo  * queue_sync_ecaps - Queue ecaps update for a (sch, cid) pair
45856fdc35bSTejun Heo  * @sch: sched to update
45956fdc35bSTejun Heo  * @cid: cid to update
46056fdc35bSTejun Heo  *
46156fdc35bSTejun Heo  * Queue an ecaps update for @sch's @cid and kick the cpu so that it syncs in
46256fdc35bSTejun Heo  * balance_one().
46356fdc35bSTejun Heo  */
46456fdc35bSTejun Heo static void queue_sync_ecaps(struct scx_sched *sch, s32 cid)
46556fdc35bSTejun Heo {
46656fdc35bSTejun Heo 	s32 cpu = __scx_cid_to_cpu(cid);
46756fdc35bSTejun Heo 	struct scx_sched_pcpu *pcpu = per_cpu_ptr(sch->pcpu, cpu);
46856fdc35bSTejun Heo 
46956fdc35bSTejun Heo 	/*
47056fdc35bSTejun Heo 	 * Pairs with smp_mb() in scx_process_sync_ecaps(). Either the check
47156fdc35bSTejun Heo 	 * below sees the node off the list and queues it, or the in-flight sync
47256fdc35bSTejun Heo 	 * sees the caps[] update made before this call.
47356fdc35bSTejun Heo 	 */
47456fdc35bSTejun Heo 	smp_mb();
47556fdc35bSTejun Heo 
47656fdc35bSTejun Heo 	/* @cid's pshard->lock excludes concurrent queueing attempts */
47756fdc35bSTejun Heo 	if (llist_on_list(&pcpu->ecaps_to_sync_node))
47856fdc35bSTejun Heo 		return;
47956fdc35bSTejun Heo 	if (llist_add(&pcpu->ecaps_to_sync_node, &cpu_rq(cpu)->scx.ecaps_to_sync))
480ce228343STejun Heo 		scx_kick_cpu(sch->ancestors[0], cpu, 0);
48156fdc35bSTejun Heo }
48256fdc35bSTejun Heo 
48356fdc35bSTejun Heo /* discard @rq's queued ecaps syncs */
48456fdc35bSTejun Heo static void discard_queued_syncs(struct rq *rq)
48556fdc35bSTejun Heo {
48656fdc35bSTejun Heo 	struct llist_node *pos, *tmp;
48756fdc35bSTejun Heo 
48856fdc35bSTejun Heo 	lockdep_assert_rq_held(rq);
48956fdc35bSTejun Heo 
49056fdc35bSTejun Heo 	llist_for_each_safe(pos, tmp, llist_del_all(&rq->scx.ecaps_to_sync))
49156fdc35bSTejun Heo 		init_llist_node(pos);
49256fdc35bSTejun Heo }
49356fdc35bSTejun Heo 
49456fdc35bSTejun Heo /**
49556fdc35bSTejun Heo  * scx_process_sync_ecaps - Sync this cpu's ecaps to pshard->caps[]
49656fdc35bSTejun Heo  * @rq: the cid's cpu rq
497b81a6c01STejun Heo  * @prev: @rq's previous task from the in-progress balance
49856fdc35bSTejun Heo  *
49956fdc35bSTejun Heo  * pshard->caps[] is the target configuration. pcpu->ecaps is the effective
50056fdc35bSTejun Heo  * transposed copy owned by the cid's cpu and written only here under @rq's
50156fdc35bSTejun Heo  * lock.
502ca3aec45STejun Heo  *
503ca3aec45STejun Heo  * A sched that newly gains baseline access here is owed an update_idle() so it
504ca3aec45STejun Heo  * learns the cid's idle state. Such a gain arms the per-rq
505ca3aec45STejun Heo  * %SCX_RQ_SUB_IDLE_RENOTIFY gate so the next idle pick delivers it.
50656fdc35bSTejun Heo  */
507b81a6c01STejun Heo void scx_process_sync_ecaps(struct rq *rq, struct task_struct *prev)
50856fdc35bSTejun Heo {
509b81a6c01STejun Heo 	s32 cpu = cpu_of(rq);
510b81a6c01STejun Heo 	s32 cid, shard;
51156fdc35bSTejun Heo 	struct llist_node *batch, *pos, *tmp;
51275a8c820STejun Heo 	u64 lost_all = 0;
51356fdc35bSTejun Heo 
51456fdc35bSTejun Heo 	lockdep_assert_rq_held(rq);
51556fdc35bSTejun Heo 
5168946dbd3STejun Heo 	if (!scx_has_subs() || likely(llist_empty(&rq->scx.ecaps_to_sync)))
51756fdc35bSTejun Heo 		return;
51856fdc35bSTejun Heo 
519b81a6c01STejun Heo 	/*
520b81a6c01STejun Heo 	 * ecaps are zeroed while the cpu is inactive and must stay zero.
521b81a6c01STejun Heo 	 * Discard queued syncs instead of processing them - the
522b81a6c01STejun Heo 	 * scx_online_ecaps() reseed re-syncs every sched on activation.
523b81a6c01STejun Heo 	 * cpu_active() clears before the offline zeroing and sets before the
524b81a6c01STejun Heo 	 * reseed is queued, so this test can neither miss a racing sync nor
525b81a6c01STejun Heo 	 * eat the reseed.
526b81a6c01STejun Heo 	 */
527b81a6c01STejun Heo 	if (unlikely(!cpu_active(cpu))) {
528b81a6c01STejun Heo 		discard_queued_syncs(rq);
529b81a6c01STejun Heo 		return;
530b81a6c01STejun Heo 	}
531b81a6c01STejun Heo 
532b81a6c01STejun Heo 	/* @cid is valid here: the cpu is active with queued syncs */
533b81a6c01STejun Heo 	cid = __scx_cpu_to_cid(cpu);
5343a773220STejun Heo 	shard = rcu_dereference_all(scx_cid_to_shard)[cid];
535b81a6c01STejun Heo 
53656fdc35bSTejun Heo 	batch = llist_del_all(&rq->scx.ecaps_to_sync);
53756fdc35bSTejun Heo 	llist_for_each_safe(pos, tmp, batch) {
53856fdc35bSTejun Heo 		struct scx_sched_pcpu *pcpu =
53956fdc35bSTejun Heo 			container_of(pos, struct scx_sched_pcpu, ecaps_to_sync_node);
54056fdc35bSTejun Heo 		struct scx_pshard *ps = pcpu->sch->pshard[shard];
541ca3aec45STejun Heo 		u64 old, ecaps, lost, gained;
54256fdc35bSTejun Heo 
54356fdc35bSTejun Heo 		init_llist_node(pos);
54456fdc35bSTejun Heo 
54556fdc35bSTejun Heo 		/* pairs with smp_mb() in queue_sync_ecaps(), see there */
54656fdc35bSTejun Heo 		smp_mb();
54756fdc35bSTejun Heo 
54875a8c820STejun Heo 		old = READ_ONCE(pcpu->ecaps);
549b81a6c01STejun Heo 		ecaps = calc_effective_caps(ps, cid);
550b81a6c01STejun Heo 		WRITE_ONCE(pcpu->ecaps, ecaps);
551b81a6c01STejun Heo 
55275a8c820STejun Heo 		lost = old & ~ecaps;
553ca3aec45STejun Heo 		gained = ecaps & ~old;
55475a8c820STejun Heo 		lost_all |= lost;
55575a8c820STejun Heo 
55634e0fbfeSTejun Heo 		/*
55734e0fbfeSTejun Heo 		 * Tell the sched its effective caps on this cid changed. The
55834e0fbfeSTejun Heo 		 * invocation is equivalent to the dispatch path and may drop
55934e0fbfeSTejun Heo 		 * and re-acquire the rq lock temporarily while the rest of
56034e0fbfeSTejun Heo 		 * @batch is held privately, see scx_discard_ecaps_to_sync().
56134e0fbfeSTejun Heo 		 */
562b81a6c01STejun Heo 		if (ecaps != pcpu->reported_ecaps &&
563b81a6c01STejun Heo 		    SCX_HAS_OP(pcpu->sch, sub_ecaps_updated) &&
564b81a6c01STejun Heo 		    !scx_bypassing(pcpu->sch, cpu)) {
565b81a6c01STejun Heo 			struct scx_dsp_ctx *dspc = &pcpu->dsp_ctx;
566b81a6c01STejun Heo 
567b81a6c01STejun Heo 			dspc->rq = rq;
568b81a6c01STejun Heo 			/* stash @prev so nested dispatches can access it */
569b81a6c01STejun Heo 			rq->scx.sub_dispatch_prev = prev;
570b81a6c01STejun Heo 			SCX_CALL_OP(pcpu->sch, sub_ecaps_updated, rq, scx_cpu_arg(cpu),
571b81a6c01STejun Heo 				    pcpu->reported_ecaps, ecaps);
572b81a6c01STejun Heo 			rq->scx.sub_dispatch_prev = NULL;
573b81a6c01STejun Heo 			scx_flush_dispatch_buf(pcpu->sch, rq);
574b81a6c01STejun Heo 			pcpu->reported_ecaps = ecaps;
575b81a6c01STejun Heo 		}
576ca3aec45STejun Heo 
577ca3aec45STejun Heo 		/*
578ca3aec45STejun Heo 		 * Gaining baseline access owes an update_idle() so the sched
579ca3aec45STejun Heo 		 * learns the cpu's idle state. Arm the per-rq gate so the next
580ca3aec45STejun Heo 		 * idle pick flushes it. Losing access drops any pending notify.
581ca3aec45STejun Heo 		 */
582ca3aec45STejun Heo 		if (gained & SCX_CAP_BASE) {
583ca3aec45STejun Heo 			pcpu->idle_renotify = true;
584ca3aec45STejun Heo 			rq->scx.flags |= SCX_RQ_SUB_IDLE_RENOTIFY;
585ca3aec45STejun Heo 		} else if (lost & SCX_CAP_BASE) {
586ca3aec45STejun Heo 			pcpu->idle_renotify = false;
587ca3aec45STejun Heo 		}
588b81a6c01STejun Heo 	}
58975a8c820STejun Heo 
59075a8c820STejun Heo 	/*
59175a8c820STejun Heo 	 * Losing a cap can strand already-queued tasks. Schedule a reenq scan
59275a8c820STejun Heo 	 * to move the now-capless ones off the local DSQ. The scan tests
59375a8c820STejun Heo 	 * against the effective caps and thus must come after the ecaps sync.
59475a8c820STejun Heo 	 */
59575a8c820STejun Heo 	if (lost_all & SCX_CAPS_REENQ_ON_LOSS)
59675a8c820STejun Heo 		scx_schedule_reenq_local(rq, SCX_REENQ_CAP_REVOKE);
597b81a6c01STejun Heo }
598b81a6c01STejun Heo 
59975c268edSTejun Heo /**
60075c268edSTejun Heo  * scx_unbypass_replay_ecaps - Replay a bypass-suppressed ecaps notification
60175c268edSTejun Heo  * @rq: rq of the cpu leaving bypass
60275c268edSTejun Heo  * @sch: scheduler that just left bypass on @rq's cpu
60375c268edSTejun Heo  *
60475c268edSTejun Heo  * scx_process_sync_ecaps() consumes syncs while bypassing without delivering
60575c268edSTejun Heo  * ops.sub_ecaps_updated(), leaving reported_ecaps stale. Nothing re-queues a
60675c268edSTejun Heo  * sync when bypass lifts, so without a replay a cid that never changes again
60775c268edSTejun Heo  * would never be notified. The attach-time initial grants are the acute case
60875c268edSTejun Heo  * as they are consumed during the enable bypass window. Re-queue a sync for
60975c268edSTejun Heo  * any undelivered delta so the next balance delivers it.
61075c268edSTejun Heo  */
61175c268edSTejun Heo void scx_unbypass_replay_ecaps(struct rq *rq, struct scx_sched *sch)
61275c268edSTejun Heo {
61375c268edSTejun Heo 	s32 cpu = cpu_of(rq);
61475c268edSTejun Heo 	struct scx_sched_pcpu *pcpu = per_cpu_ptr(sch->pcpu, cpu);
61575c268edSTejun Heo 	struct scx_pshard *ps;
61675c268edSTejun Heo 	s32 cid;
61775c268edSTejun Heo 
61875c268edSTejun Heo 	lockdep_assert_rq_held(rq);
61975c268edSTejun Heo 
62075c268edSTejun Heo 	/* root holds every cap and never uses ecaps */
62175c268edSTejun Heo 	if (!sch->level)
62275c268edSTejun Heo 		return;
62375c268edSTejun Heo 
62475c268edSTejun Heo 	if (READ_ONCE(pcpu->ecaps) == pcpu->reported_ecaps)
62575c268edSTejun Heo 		return;
62675c268edSTejun Heo 
62775c268edSTejun Heo 	cid = __scx_cpu_to_cid(cpu);
6283a773220STejun Heo 	ps = sch->pshard[rcu_dereference_all(scx_cid_to_shard)[cid]];
62975c268edSTejun Heo 
63075c268edSTejun Heo 	guard(raw_spinlock)(&ps->lock);
63175c268edSTejun Heo 	queue_sync_ecaps(sch, cid);
63275c268edSTejun Heo }
63375c268edSTejun Heo 
634b81a6c01STejun Heo /*
635b81a6c01STejun Heo  * A cpu came back. Re-seed each sub-sched's ecaps on the cpu's cid. The sync
636b81a6c01STejun Heo  * recomputes effective caps from the pshard and fires ops.sub_ecaps_updated()
637b81a6c01STejun Heo  * only on a real change since offline.
638b81a6c01STejun Heo  */
639b81a6c01STejun Heo void scx_online_ecaps(struct rq *rq)
640b81a6c01STejun Heo {
641ce228343STejun Heo 	struct scx_sched *root, *pos;
6423a773220STejun Heo 	s32 cid, shard;
6433a773220STejun Heo 
6443a773220STejun Heo 	/*
6453a773220STejun Heo 	 * Only a live hierarchy can have ecaps to reseed. This also keeps the
6463a773220STejun Heo 	 * table reads below away from an enable that failed before publishing
6473a773220STejun Heo 	 * the tables. A concurrent disable can't retire them, see
6483a773220STejun Heo 	 * handle_hotplug().
6493a773220STejun Heo 	 */
6503a773220STejun Heo 	if (!scx_enabled())
6513a773220STejun Heo 		return;
652b81a6c01STejun Heo 
653b81a6c01STejun Heo 	guard(rq_lock_irqsave)(rq);
654b81a6c01STejun Heo 
655ce228343STejun Heo 	root = scx_root_protected();
6563a773220STejun Heo 	cid = __scx_cpu_to_cid(cpu_of(rq));
6573a773220STejun Heo 	shard = rcu_dereference_all(scx_cid_to_shard)[cid];
6583a773220STejun Heo 
659ce228343STejun Heo 	scx_for_each_descendant_pre(pos, root) {
660b81a6c01STejun Heo 		struct scx_pshard *ps;
661b81a6c01STejun Heo 
662b81a6c01STejun Heo 		/* root holds every cap and never uses ecaps */
663ce228343STejun Heo 		if (!pos->level)
664b81a6c01STejun Heo 			continue;
665b81a6c01STejun Heo 
666b81a6c01STejun Heo 		ps = pos->pshard[shard];
667b81a6c01STejun Heo 		guard(raw_spinlock)(&ps->lock);
668b81a6c01STejun Heo 		queue_sync_ecaps(pos, cid);
669b81a6c01STejun Heo 	}
670b81a6c01STejun Heo }
671b81a6c01STejun Heo 
672b81a6c01STejun Heo /*
673b81a6c01STejun Heo  * A cpu is going down. Zero each sub-sched's in-effect ecaps so cap checks
674b81a6c01STejun Heo  * treat the cpu as capless while offline. Pending and late-queued syncs are
675b81a6c01STejun Heo  * discarded at consumption by scx_process_sync_ecaps() while the cpu is
676b81a6c01STejun Heo  * inactive. Leave reported_ecaps. Ownership is unchanged, so the
677b81a6c01STejun Heo  * scx_online_ecaps() reseed reports only a genuine delta. No callback fires
678b81a6c01STejun Heo  * here.
679b81a6c01STejun Heo  */
680b81a6c01STejun Heo void scx_offline_ecaps(struct rq *rq)
681b81a6c01STejun Heo {
682b81a6c01STejun Heo 	s32 cpu = cpu_of(rq);
683ce228343STejun Heo 	struct scx_sched *root, *pos;
684b81a6c01STejun Heo 
685b81a6c01STejun Heo 	guard(rq_lock_irqsave)(rq);
686b81a6c01STejun Heo 
687ce228343STejun Heo 	root = scx_root_protected();
688ce228343STejun Heo 
689ce228343STejun Heo 	scx_for_each_descendant_pre(pos, root) {
690b81a6c01STejun Heo 		/* root holds every cap and never uses ecaps */
691ce228343STejun Heo 		if (!pos->level)
692b81a6c01STejun Heo 			continue;
693b81a6c01STejun Heo 
694b81a6c01STejun Heo 		WRITE_ONCE(per_cpu_ptr(pos->pcpu, cpu)->ecaps, 0);
69556fdc35bSTejun Heo 	}
69656fdc35bSTejun Heo }
69756fdc35bSTejun Heo 
69856fdc35bSTejun Heo /*
69934e0fbfeSTejun Heo  * @pcpu's sched was unhashed before the grace period, so nothing re-queues its
70034e0fbfeSTejun Heo  * sync node. Remove the node from @rq's pending list so the pcpu can be freed.
70156fdc35bSTejun Heo  */
70256fdc35bSTejun Heo void scx_discard_ecaps_to_sync(s32 cpu, struct scx_sched_pcpu *pcpu)
70356fdc35bSTejun Heo {
704b81a6c01STejun Heo 	struct rq *rq = cpu_rq(cpu);
70534e0fbfeSTejun Heo 	struct llist_node *head = NULL, *tail = NULL;
70634e0fbfeSTejun Heo 	struct llist_node *pos, *tmp;
70756fdc35bSTejun Heo 
70834e0fbfeSTejun Heo 	/*
70934e0fbfeSTejun Heo 	 * llist can't unlink a single node. Take all queued nodes, drop @pcpu's
71034e0fbfeSTejun Heo 	 * and resplice the rest. Nodes in the taken batch read as on-list
71134e0fbfeSTejun Heo 	 * throughout, so queue_sync_ecaps() stays correct.
71234e0fbfeSTejun Heo 	 */
71334e0fbfeSTejun Heo 	if (llist_on_list(&pcpu->ecaps_to_sync_node)) {
71434e0fbfeSTejun Heo 		scoped_guard (rq_lock_irqsave, rq) {
71534e0fbfeSTejun Heo 			llist_for_each_safe(pos, tmp, llist_del_all(&rq->scx.ecaps_to_sync)) {
71634e0fbfeSTejun Heo 				if (pos == &pcpu->ecaps_to_sync_node) {
71734e0fbfeSTejun Heo 					init_llist_node(pos);
71834e0fbfeSTejun Heo 				} else {
71934e0fbfeSTejun Heo 					pos->next = head;
72034e0fbfeSTejun Heo 					head = pos;
72134e0fbfeSTejun Heo 					if (!tail)
72234e0fbfeSTejun Heo 						tail = pos;
72334e0fbfeSTejun Heo 				}
72434e0fbfeSTejun Heo 			}
72534e0fbfeSTejun Heo 			if (head)
72634e0fbfeSTejun Heo 				llist_add_batch(head, tail, &rq->scx.ecaps_to_sync);
72734e0fbfeSTejun Heo 		}
72834e0fbfeSTejun Heo 	}
72934e0fbfeSTejun Heo 
73034e0fbfeSTejun Heo 	/*
73134e0fbfeSTejun Heo 	 * An in-flight scx_process_sync_ecaps() batch may still hold the node
73234e0fbfeSTejun Heo 	 * privately across dispatch-induced rq unlocks, reading as on-list.
73334e0fbfeSTejun Heo 	 *
73434e0fbfeSTejun Heo 	 * Because a bypassing sched gets no op call, init_llist_node() and all
73534e0fbfeSTejun Heo 	 * @pcpu accesses share one contiguous lock hold, off-list under the rq
73634e0fbfeSTejun Heo 	 * lock means @pcpu won't be accessed again.
73734e0fbfeSTejun Heo 	 */
738b81a6c01STejun Heo 	while (true) {
739b81a6c01STejun Heo 		scoped_guard (rq_lock_irqsave, rq) {
740b81a6c01STejun Heo 			if (!llist_on_list(&pcpu->ecaps_to_sync_node))
741b81a6c01STejun Heo 				return;
742b81a6c01STejun Heo 		}
74334e0fbfeSTejun Heo 		cpu_relax();
744b81a6c01STejun Heo 	}
74556fdc35bSTejun Heo }
74656fdc35bSTejun Heo 
74756fdc35bSTejun Heo /**
74856fdc35bSTejun Heo  * scx_discard_stale_ecaps_syncs - Discard ecaps syncs from earlier schedulers
74956fdc35bSTejun Heo  *
75056fdc35bSTejun Heo  * To be called during root enable before the scheduler goes live. An earlier
75156fdc35bSTejun Heo  * root's sub-sched may not have gone through its RCU free path yet (e.g. a
75256fdc35bSTejun Heo  * still-open link fd defers it) and can leave queued ecaps syncs behind.
75356fdc35bSTejun Heo  * Processing them would decode the dead sched's pshards with the current cid
75456fdc35bSTejun Heo  * layout. Discard them instead. The backing scx_sched_pcpu's are still
75534e0fbfeSTejun Heo  * allocated as the free path removes ecaps_to_sync_node before freeing.
75656fdc35bSTejun Heo  */
75756fdc35bSTejun Heo void scx_discard_stale_ecaps_syncs(void)
75856fdc35bSTejun Heo {
75956fdc35bSTejun Heo 	s32 cpu;
76056fdc35bSTejun Heo 
76156fdc35bSTejun Heo 	for_each_possible_cpu(cpu) {
76256fdc35bSTejun Heo 		struct rq *rq = cpu_rq(cpu);
76356fdc35bSTejun Heo 
76456fdc35bSTejun Heo 		guard(rq_lock_irqsave)(rq);
76556fdc35bSTejun Heo 		discard_queued_syncs(rq);
76656fdc35bSTejun Heo 	}
76756fdc35bSTejun Heo }
76856fdc35bSTejun Heo 
769daf8e166STejun Heo static DECLARE_WAIT_QUEUE_HEAD(scx_unlink_waitq);
770daf8e166STejun Heo 
771daf8e166STejun Heo void drain_descendants(struct scx_sched *sch)
772daf8e166STejun Heo {
773daf8e166STejun Heo 	/*
774daf8e166STejun Heo 	 * Child scheds that finished the critical part of disabling will take
775daf8e166STejun Heo 	 * themselves off @sch->children. Wait for it to drain. As propagation
776daf8e166STejun Heo 	 * is recursive, empty @sch->children means that all proper descendant
777daf8e166STejun Heo 	 * scheds reached unlinking stage.
778daf8e166STejun Heo 	 */
779daf8e166STejun Heo 	wait_event(scx_unlink_waitq, list_empty(&sch->children));
780daf8e166STejun Heo }
781daf8e166STejun Heo 
7820dc90ce1STejun Heo /**
7830dc90ce1STejun Heo  * scx_rehome_task - Move a task to a sched it has been initialized for
7840dc90ce1STejun Heo  * @to: sched taking over @p, @p's init on it already complete
7850dc90ce1STejun Heo  * @p: task to re-home
7860dc90ce1STejun Heo  *
7870dc90ce1STejun Heo  * Exit @p from its current sched and switch it over to @to, overriding the
7880dc90ce1STejun Heo  * state to %SCX_TASK_READY to account for the already completed init. A task
7890dc90ce1STejun Heo  * on a non-ext class, possible under an %SCX_OPS_SWITCH_PARTIAL root, stays
7900dc90ce1STejun Heo  * %READY and is enabled by switching_to_scx() if it switches over.
7910dc90ce1STejun Heo  */
7920dc90ce1STejun Heo static void scx_rehome_task(struct scx_sched *to, struct task_struct *p)
7930dc90ce1STejun Heo {
7940dc90ce1STejun Heo 	lockdep_assert_held(&p->pi_lock);
7950dc90ce1STejun Heo 	lockdep_assert_rq_held(task_rq(p));
7960dc90ce1STejun Heo 
7970dc90ce1STejun Heo 	scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE) {
7980dc90ce1STejun Heo 		scx_disable_and_exit_task(scx_task_sched(p), p);
7990dc90ce1STejun Heo 		scx_set_task_state(p, SCX_TASK_INIT_BEGIN);
8000dc90ce1STejun Heo 		scx_set_task_state(p, SCX_TASK_INIT);
8010dc90ce1STejun Heo 		scx_set_task_sched(p, to);
8020dc90ce1STejun Heo 		scx_set_task_state(p, SCX_TASK_READY);
8030dc90ce1STejun Heo 		if (p->sched_class == &ext_sched_class)
8040dc90ce1STejun Heo 			scx_enable_task(to, p);
8050dc90ce1STejun Heo 	}
8060dc90ce1STejun Heo }
8070dc90ce1STejun Heo 
8080dc90ce1STejun Heo /**
8090dc90ce1STejun Heo  * scx_punt_task - Hand a task to a failed sched without initialization
8100dc90ce1STejun Heo  * @to: failed and bypassed sched taking custody of @p
8110dc90ce1STejun Heo  * @p: task to punt
8120dc90ce1STejun Heo  *
8130dc90ce1STejun Heo  * Take @p off its current sched and put it on @to at %SCX_TASK_NONE. @to is
8140dc90ce1STejun Heo  * dying and its teardown will re-home @p properly.
8150dc90ce1STejun Heo  *
8160dc90ce1STejun Heo  * Used when @to must take over @p but failed to initialize it. Bypass keeps
8170dc90ce1STejun Heo  * scheduling decisions away from @to but @p can still trigger its task ops,
8180dc90ce1STejun Heo  * which may confuse the BPF side. @to is dying anyway. The exit paths skip
8190dc90ce1STejun Heo  * %NONE tasks (see __scx_disable_and_exit_task() and switched_from_scx()).
8200dc90ce1STejun Heo  */
8210dc90ce1STejun Heo static void scx_punt_task(struct scx_sched *to, struct task_struct *p)
8220dc90ce1STejun Heo {
8230dc90ce1STejun Heo 	lockdep_assert_held(&p->pi_lock);
8240dc90ce1STejun Heo 	lockdep_assert_rq_held(task_rq(p));
8250dc90ce1STejun Heo 	WARN_ON_ONCE(!READ_ONCE(to->bypass_depth));
8260dc90ce1STejun Heo 
8270dc90ce1STejun Heo 	scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE) {
8280dc90ce1STejun Heo 		scx_disable_and_exit_task(scx_task_sched(p), p);
8290dc90ce1STejun Heo 		scx_set_task_sched(p, to);
8300dc90ce1STejun Heo 	}
8310dc90ce1STejun Heo }
8320dc90ce1STejun Heo 
833daf8e166STejun Heo static void scx_fail_parent(struct scx_sched *sch,
834daf8e166STejun Heo 			    struct task_struct *failed, s32 fail_code)
835daf8e166STejun Heo {
836daf8e166STejun Heo 	struct scx_sched *parent = scx_parent(sch);
837daf8e166STejun Heo 	struct scx_task_iter sti;
838daf8e166STejun Heo 	struct task_struct *p;
839daf8e166STejun Heo 
840daf8e166STejun Heo 	scx_error(parent, "ops.init_task() failed (%d) for %s[%d] while disabling a sub-scheduler",
841daf8e166STejun Heo 		  fail_code, failed->comm, failed->pid);
842daf8e166STejun Heo 
843daf8e166STejun Heo 	/*
8440dc90ce1STejun Heo 	 * Once $parent is bypassed, tasks can be punted into it. This may
8450dc90ce1STejun Heo 	 * cause downstream failures on the BPF side but $parent is dying
8460dc90ce1STejun Heo 	 * anyway.
847daf8e166STejun Heo 	 */
848daf8e166STejun Heo 	scx_bypass(parent, true);
849daf8e166STejun Heo 
850daf8e166STejun Heo 	scx_task_iter_start(&sti, sch->cgrp);
851daf8e166STejun Heo 	while ((p = scx_task_iter_next_locked(&sti))) {
852daf8e166STejun Heo 		if (scx_task_on_sched(parent, p))
853daf8e166STejun Heo 			continue;
854daf8e166STejun Heo 
8550dc90ce1STejun Heo 		scx_punt_task(parent, p);
856daf8e166STejun Heo 	}
857daf8e166STejun Heo 	scx_task_iter_stop(&sti);
858daf8e166STejun Heo }
859daf8e166STejun Heo 
860a6ec0b62STejun Heo #ifdef CONFIG_EXT_GROUP_SCHED
861a6ec0b62STejun Heo /**
862a6ec0b62STejun Heo  * scx_cgroup_claim_subtree - Claim the subtree's cgroups for an enabling sub
863a6ec0b62STejun Heo  * @sch: sub-scheduler being enabled
864a6ec0b62STejun Heo  *
865a6ec0b62STejun Heo  * Called while enabling @sch, after the subtree's cgrp->scx_sched's are pointed
866a6ec0b62STejun Heo  * at @sch and before any task is claimed. This mirrors root enable's
867a6ec0b62STejun Heo  * cgroups-before-tasks order. The ops.init_task() args are task_group-granular
868a6ec0b62STejun Heo  * and can still reference a cgroup outside the handed-over set when the cpu
869a6ec0b62STejun Heo  * controller is coarser than the sub topology or mounted on cgroup1.
870a6ec0b62STejun Heo  *
871a6ec0b62STejun Heo  * First init each of the parent sched's subtree cgroups on @sch, and only then
872a6ec0b62STejun Heo  * exit them from the parent, so that a failed init can be unwound with the
873a6ec0b62STejun Heo  * parent untouched. The both-inited transient is invisible outside
874a6ec0b62STejun Heo  * scx_cgroup_lock(). %SCX_TG_SUB_INIT tracks the first pass's progress.
875a6ec0b62STejun Heo  * %SCX_TG_INITED stays set throughout, except for a task_group whose
876a6ec0b62STejun Heo  * ops.cgroup_init() failed on the parent (see scx_cgroup_return_subtree()):
877a6ec0b62STejun Heo  * there is nothing to exit from the parent and %SCX_TG_INITED is set back with
878a6ec0b62STejun Heo  * the transfer.
879a6ec0b62STejun Heo  *
880a6ec0b62STejun Heo  * Dying but not yet offlined task_groups are included: a removed cgroup keeps
881a6ec0b62STejun Heo  * hosting scheduling events until its dying tasks finish their final context
882a6ec0b62STejun Heo  * switches, so it still needs to be inited on a sched, and its offline-time
883a6ec0b62STejun Heo  * ops.cgroup_exit() follows the last of those events.
884a6ec0b62STejun Heo  *
885a6ec0b62STejun Heo  * Return 0 on success, -errno on failure. On failure, @sch has been
886a6ec0b62STejun Heo  * scx_error()'d and is left with no cgroups.
887a6ec0b62STejun Heo  */
888a6ec0b62STejun Heo static s32 scx_cgroup_claim_subtree(struct scx_sched *sch)
889a6ec0b62STejun Heo {
890a6ec0b62STejun Heo 	struct cgroup *sub_cgrp = sch_cgroup(sch);
891a6ec0b62STejun Heo 	struct cgroup_subsys_state *ecss = cgroup_e_css(sub_cgrp, &cpu_cgrp_subsys);
892a6ec0b62STejun Heo 	struct scx_sched *parent = scx_parent(sch);
893a6ec0b62STejun Heo 	struct cgroup_subsys_state *css;
894a6ec0b62STejun Heo 	int ret;
895a6ec0b62STejun Heo 
896a6ec0b62STejun Heo 	css_for_each_descendant_pre(css, ecss) {
897a6ec0b62STejun Heo 		struct task_group *tg = css_tg(css);
898a6ec0b62STejun Heo 		struct scx_cgroup_init_args args = {
899a6ec0b62STejun Heo 			.weight = tg->scx.weight,
900a6ec0b62STejun Heo 			.bw_period_us = tg->scx.bw_period_us,
901a6ec0b62STejun Heo 			.bw_quota_us = tg->scx.bw_quota_us,
902a6ec0b62STejun Heo 			.bw_burst_us = tg->scx.bw_burst_us,
903a6ec0b62STejun Heo 		};
904a6ec0b62STejun Heo 
905a6ec0b62STejun Heo 		if (tg->scx.sched != parent ||
906a6ec0b62STejun Heo 		    !cgroup_is_descendant(css->cgroup, sub_cgrp))
907a6ec0b62STejun Heo 			continue;
908a6ec0b62STejun Heo 
909a6ec0b62STejun Heo 		if (SCX_HAS_OP(sch, cgroup_init)) {
910a6ec0b62STejun Heo 			ret = SCX_CALL_OP_RET(sch, cgroup_init, NULL, css->cgroup, &args);
911a6ec0b62STejun Heo 			if (ret) {
912a6ec0b62STejun Heo 				scx_error(sch, "ops.cgroup_init() failed (%d)", ret);
913a6ec0b62STejun Heo 				goto err;
914a6ec0b62STejun Heo 			}
915a6ec0b62STejun Heo 		}
916a6ec0b62STejun Heo 		tg->scx.flags |= SCX_TG_SUB_INIT;
917a6ec0b62STejun Heo 	}
918a6ec0b62STejun Heo 
919a6ec0b62STejun Heo 	css_for_each_descendant_post(css, ecss) {
920a6ec0b62STejun Heo 		struct task_group *tg = css_tg(css);
921a6ec0b62STejun Heo 
922a6ec0b62STejun Heo 		/*
923a6ec0b62STejun Heo 		 * SUB_INIT is pass 1's progress mark: pass 2 and the err path
924a6ec0b62STejun Heo 		 * must visit exactly the tgs pass 1 inited.
925a6ec0b62STejun Heo 		 */
926a6ec0b62STejun Heo 		if (!(tg->scx.flags & SCX_TG_SUB_INIT))
927a6ec0b62STejun Heo 			continue;
928a6ec0b62STejun Heo 
929a6ec0b62STejun Heo 		/* skip the exit if the parent's ops.cgroup_init() failed */
930a6ec0b62STejun Heo 		if ((tg->scx.flags & SCX_TG_INITED) && SCX_HAS_OP(parent, cgroup_exit))
931a6ec0b62STejun Heo 			SCX_CALL_OP(parent, cgroup_exit, NULL, css->cgroup);
932a6ec0b62STejun Heo 		tg->scx.sched = sch;
933a6ec0b62STejun Heo 		tg->scx.flags |= SCX_TG_INITED;
934a6ec0b62STejun Heo 		tg->scx.flags &= ~SCX_TG_SUB_INIT;
935a6ec0b62STejun Heo 	}
936a6ec0b62STejun Heo 
937a6ec0b62STejun Heo 	return 0;
938a6ec0b62STejun Heo 
939a6ec0b62STejun Heo err:
940a6ec0b62STejun Heo 	css_for_each_descendant_post(css, ecss) {
941a6ec0b62STejun Heo 		struct task_group *tg = css_tg(css);
942a6ec0b62STejun Heo 
943a6ec0b62STejun Heo 		if (!(tg->scx.flags & SCX_TG_SUB_INIT))
944a6ec0b62STejun Heo 			continue;
945a6ec0b62STejun Heo 
946a6ec0b62STejun Heo 		if (SCX_HAS_OP(sch, cgroup_exit))
947a6ec0b62STejun Heo 			SCX_CALL_OP(sch, cgroup_exit, NULL, css->cgroup);
948a6ec0b62STejun Heo 		tg->scx.flags &= ~SCX_TG_SUB_INIT;
949a6ec0b62STejun Heo 	}
950a6ec0b62STejun Heo 	return ret;
951a6ec0b62STejun Heo }
952a6ec0b62STejun Heo 
953a6ec0b62STejun Heo /**
954a6ec0b62STejun Heo  * scx_cgroup_return_subtree - Return the subtree's cgroups to the parent sched
955a6ec0b62STejun Heo  * @sch: sub-scheduler being disabled
956a6ec0b62STejun Heo  *
957a6ec0b62STejun Heo  * Called while disabling @sch, after the subtree's cgrp->scx_sched's are reset
958a6ec0b62STejun Heo  * to the parent sched and before tasks are re-homed, mirroring root disable's
959a6ec0b62STejun Heo  * cgroups-before-tasks teardown order. The reverse of
960a6ec0b62STejun Heo  * scx_cgroup_claim_subtree(): exit @sch's cgroups from @sch, then init them on
961a6ec0b62STejun Heo  * the parent with the current tg->scx.* values, resyncing settings that changed
962a6ec0b62STejun Heo  * while @sch had them.
963a6ec0b62STejun Heo  *
964a6ec0b62STejun Heo  * When an init on the parent fails, the parent is failed - the same policy as
965a6ec0b62STejun Heo  * task re-homing. The remaining task_groups are punted: they move to the parent
966a6ec0b62STejun Heo  * anyway with %SCX_TG_INITED cleared, as ops.cgroup_init() failed or never ran
967a6ec0b62STejun Heo  * for them. A punted task_group gets no cgroup ops. The dying parent's own
968a6ec0b62STejun Heo  * disable moves it one sched up, initing it there. Root ends the chain: root
969a6ec0b62STejun Heo  * teardown drops cgroup ops entirely and the next enable's bulk init re-inits
970a6ec0b62STejun Heo  * every online task_group.
971a6ec0b62STejun Heo  *
972a6ec0b62STejun Heo  * The task re-home that follows still delivers ops.init_task() to the dying
973a6ec0b62STejun Heo  * parent, including for tasks in punted cgroups it never inited - tolerated
974a6ec0b62STejun Heo  * like the downstream failures of task punting (see scx_punt_task()).
975a6ec0b62STejun Heo  */
976a6ec0b62STejun Heo static void scx_cgroup_return_subtree(struct scx_sched *sch)
977a6ec0b62STejun Heo {
978a6ec0b62STejun Heo 	struct cgroup *sub_cgrp = sch_cgroup(sch);
979a6ec0b62STejun Heo 	struct cgroup_subsys_state *ecss = cgroup_e_css(sub_cgrp, &cpu_cgrp_subsys);
980a6ec0b62STejun Heo 	struct scx_sched *parent = scx_parent(sch);
981a6ec0b62STejun Heo 	struct cgroup_subsys_state *css;
982a6ec0b62STejun Heo 	bool parent_failed = false;
983a6ec0b62STejun Heo 	int ret;
984a6ec0b62STejun Heo 
985a6ec0b62STejun Heo 	css_for_each_descendant_post(css, ecss) {
986a6ec0b62STejun Heo 		struct task_group *tg = css_tg(css);
987a6ec0b62STejun Heo 
988a6ec0b62STejun Heo 		if (tg->scx.sched != sch ||
989a6ec0b62STejun Heo 		    !cgroup_is_descendant(css->cgroup, sub_cgrp))
990a6ec0b62STejun Heo 			continue;
991a6ec0b62STejun Heo 
992a6ec0b62STejun Heo 		/* skip the exit if @sch's ops.cgroup_init() failed for the tg */
993a6ec0b62STejun Heo 		if ((tg->scx.flags & SCX_TG_INITED) && SCX_HAS_OP(sch, cgroup_exit))
994a6ec0b62STejun Heo 			SCX_CALL_OP(sch, cgroup_exit, NULL, css->cgroup);
995a6ec0b62STejun Heo 		tg->scx.sched = parent;
996a6ec0b62STejun Heo 		tg->scx.flags |= SCX_TG_SUB_INIT;
997a6ec0b62STejun Heo 	}
998a6ec0b62STejun Heo 
999a6ec0b62STejun Heo 	css_for_each_descendant_pre(css, ecss) {
1000a6ec0b62STejun Heo 		struct task_group *tg = css_tg(css);
1001a6ec0b62STejun Heo 		struct scx_cgroup_init_args args = {
1002a6ec0b62STejun Heo 			.weight = tg->scx.weight,
1003a6ec0b62STejun Heo 			.bw_period_us = tg->scx.bw_period_us,
1004a6ec0b62STejun Heo 			.bw_quota_us = tg->scx.bw_quota_us,
1005a6ec0b62STejun Heo 			.bw_burst_us = tg->scx.bw_burst_us,
1006a6ec0b62STejun Heo 		};
1007a6ec0b62STejun Heo 
1008a6ec0b62STejun Heo 		/* the first pass must have transferred everything */
1009a6ec0b62STejun Heo 		WARN_ON_ONCE(tg->scx.sched == sch);
1010a6ec0b62STejun Heo 
1011a6ec0b62STejun Heo 		/*
1012a6ec0b62STejun Heo 		 * SUB_INIT distinguishes the tgs pass 1 moved. The sched test
1013a6ec0b62STejun Heo 		 * can't: a tg punted to the parent by an earlier failure would
1014a6ec0b62STejun Heo 		 * also match.
1015a6ec0b62STejun Heo 		 */
1016a6ec0b62STejun Heo 		if (!(tg->scx.flags & SCX_TG_SUB_INIT))
1017a6ec0b62STejun Heo 			continue;
1018a6ec0b62STejun Heo 		tg->scx.flags &= ~(SCX_TG_SUB_INIT | SCX_TG_INITED);
1019a6ec0b62STejun Heo 
1020a6ec0b62STejun Heo 		/*
1021a6ec0b62STejun Heo 		 * A re-init on $parent failed. The task_groups from here on are
1022a6ec0b62STejun Heo 		 * punted: they stay on the dying $parent with INITED clear and
1023a6ec0b62STejun Heo 		 * move onward when it disables.
1024a6ec0b62STejun Heo 		 */
1025a6ec0b62STejun Heo 		if (parent_failed)
1026a6ec0b62STejun Heo 			continue;
1027a6ec0b62STejun Heo 
1028a6ec0b62STejun Heo 		if (SCX_HAS_OP(parent, cgroup_init)) {
1029a6ec0b62STejun Heo 			ret = SCX_CALL_OP_RET(parent, cgroup_init, NULL, css->cgroup, &args);
1030a6ec0b62STejun Heo 			if (ret) {
1031a6ec0b62STejun Heo 				scx_error(parent, "ops.cgroup_init() failed (%d) while disabling a sub-scheduler",
1032a6ec0b62STejun Heo 					  ret);
1033a6ec0b62STejun Heo 				parent_failed = true;
1034a6ec0b62STejun Heo 				continue;
1035a6ec0b62STejun Heo 			}
1036a6ec0b62STejun Heo 		}
1037a6ec0b62STejun Heo 		tg->scx.flags |= SCX_TG_INITED;
1038a6ec0b62STejun Heo 	}
1039a6ec0b62STejun Heo }
1040a6ec0b62STejun Heo #else
1041a6ec0b62STejun Heo static inline s32 scx_cgroup_claim_subtree(struct scx_sched *sch) { return 0; }
1042a6ec0b62STejun Heo static inline void scx_cgroup_return_subtree(struct scx_sched *sch) {}
1043a6ec0b62STejun Heo #endif
1044a6ec0b62STejun Heo 
1045daf8e166STejun Heo void scx_sub_disable(struct scx_sched *sch)
1046daf8e166STejun Heo {
1047daf8e166STejun Heo 	struct scx_sched *parent = scx_parent(sch);
1048daf8e166STejun Heo 	struct scx_task_iter sti;
1049daf8e166STejun Heo 	struct task_struct *p;
1050daf8e166STejun Heo 	int ret;
1051daf8e166STejun Heo 
1052daf8e166STejun Heo 	/*
1053daf8e166STejun Heo 	 * Guarantee forward progress and wait for descendants to be disabled.
1054daf8e166STejun Heo 	 * To limit disruptions, $parent is not bypassed. Tasks are fully
1055daf8e166STejun Heo 	 * prepped and then inserted back into $parent.
1056daf8e166STejun Heo 	 */
1057daf8e166STejun Heo 	scx_bypass(sch, true);
1058daf8e166STejun Heo 	drain_descendants(sch);
1059daf8e166STejun Heo 
1060daf8e166STejun Heo 	/*
1061daf8e166STejun Heo 	 * Here, every runnable task is guaranteed to make forward progress and
1062daf8e166STejun Heo 	 * we can safely use blocking synchronization constructs. Actually
1063daf8e166STejun Heo 	 * disable ops.
1064daf8e166STejun Heo 	 */
1065daf8e166STejun Heo 	mutex_lock(&scx_enable_mutex);
1066daf8e166STejun Heo 	percpu_down_write(&scx_fork_rwsem);
1067daf8e166STejun Heo 	scx_cgroup_lock();
1068daf8e166STejun Heo 
10697c2cd767STejun Heo 	/*
1070*f883dbb6STejun Heo 	 * An enable that failed before scx_link_sched() succeeded never owned a
1071*f883dbb6STejun Heo 	 * cgroup or task and won't be waited on by an ancestor's
1072*f883dbb6STejun Heo 	 * drain_descendants(). Nothing to reparent and walking the tasks can
1073*f883dbb6STejun Heo 	 * misbehave as the task ownership invariant (either owned by self or
1074*f883dbb6STejun Heo 	 * parent) does not hold. ->sibling can't identify this case - an undone
1075*f883dbb6STejun Heo 	 * link leaves it non-empty.
10767c2cd767STejun Heo 	 */
1077*f883dbb6STejun Heo 	if (!sch->linked)
10787c2cd767STejun Heo 		goto dump;
10797c2cd767STejun Heo 
1080daf8e166STejun Heo 	set_cgroup_sched(sch_cgroup(sch), parent);
1081daf8e166STejun Heo 
1082a6ec0b62STejun Heo 	/*
1083a6ec0b62STejun Heo 	 * Return the subtree's cgroups before re-homing tasks so that any
1084a6ec0b62STejun Heo 	 * ops.init_task() on $parent only sees cgroups it has initialized.
1085a6ec0b62STejun Heo 	 */
1086a6ec0b62STejun Heo 	scx_cgroup_return_subtree(sch);
1087a6ec0b62STejun Heo 
1088daf8e166STejun Heo 	scx_task_iter_start(&sti, sch->cgrp);
1089daf8e166STejun Heo 	while ((p = scx_task_iter_next_locked(&sti))) {
1090daf8e166STejun Heo 		struct rq *rq;
1091daf8e166STejun Heo 		struct rq_flags rf;
1092daf8e166STejun Heo 
1093daf8e166STejun Heo 		/* filter out duplicate visits */
1094daf8e166STejun Heo 		if (scx_task_on_sched(parent, p))
1095daf8e166STejun Heo 			continue;
1096daf8e166STejun Heo 
1097daf8e166STejun Heo 		/*
10987c2cd767STejun Heo 		 * By the time control reaches here, all linked descendant
10997c2cd767STejun Heo 		 * schedulers should have been disabled.
1100daf8e166STejun Heo 		 */
1101daf8e166STejun Heo 		WARN_ON_ONCE(!scx_task_on_sched(sch, p));
1102daf8e166STejun Heo 
1103daf8e166STejun Heo 		/*
1104daf8e166STejun Heo 		 * @p is pinned by the iter: css_task_iter_next() takes a
1105daf8e166STejun Heo 		 * reference and holds it until the next iter_next() call, so
1106daf8e166STejun Heo 		 * @p->usage is guaranteed > 0.
1107daf8e166STejun Heo 		 */
1108daf8e166STejun Heo 		get_task_struct(p);
1109daf8e166STejun Heo 
1110daf8e166STejun Heo 		scx_task_iter_unlock(&sti);
1111daf8e166STejun Heo 
1112daf8e166STejun Heo 		/*
1113daf8e166STejun Heo 		 * $p is READY or ENABLED on @sch. Initialize for $parent,
1114daf8e166STejun Heo 		 * disable and exit from @sch, and then switch over to $parent.
1115daf8e166STejun Heo 		 *
1116daf8e166STejun Heo 		 * If a task fails to initialize for $parent, the only available
1117daf8e166STejun Heo 		 * action is disabling $parent too. While this allows disabling
1118daf8e166STejun Heo 		 * of a child sched to cause the parent scheduler to fail, the
1119daf8e166STejun Heo 		 * failure can only originate from ops.init_task() of the
1120daf8e166STejun Heo 		 * parent. A child can't directly affect the parent through its
1121daf8e166STejun Heo 		 * own failures.
1122daf8e166STejun Heo 		 */
1123bf9dee58STejun Heo 		ret = __scx_init_task(parent, p, NULL, false);
1124daf8e166STejun Heo 		if (ret) {
1125daf8e166STejun Heo 			scx_fail_parent(sch, p, ret);
1126daf8e166STejun Heo 			put_task_struct(p);
1127daf8e166STejun Heo 			break;
1128daf8e166STejun Heo 		}
1129daf8e166STejun Heo 
1130daf8e166STejun Heo 		rq = task_rq_lock(p, &rf);
1131daf8e166STejun Heo 
1132daf8e166STejun Heo 		if (scx_get_task_state(p) == SCX_TASK_DEAD) {
1133daf8e166STejun Heo 			/*
1134daf8e166STejun Heo 			 * sched_ext_dead() raced us between __scx_init_task()
1135daf8e166STejun Heo 			 * and this rq lock and ran exit_task() on @sch (the
1136daf8e166STejun Heo 			 * sched @p was on at that point), not on $parent.
1137daf8e166STejun Heo 			 * $parent's just-completed init is owed an exit_task()
1138daf8e166STejun Heo 			 * and we issue it here.
1139daf8e166STejun Heo 			 */
1140daf8e166STejun Heo 			scx_sub_init_cancel_task(parent, p);
1141daf8e166STejun Heo 			task_rq_unlock(rq, p, &rf);
1142daf8e166STejun Heo 			put_task_struct(p);
1143daf8e166STejun Heo 			continue;
1144daf8e166STejun Heo 		}
1145daf8e166STejun Heo 
11460dc90ce1STejun Heo 		scx_rehome_task(parent, p);
1147daf8e166STejun Heo 
1148daf8e166STejun Heo 		task_rq_unlock(rq, p, &rf);
1149daf8e166STejun Heo 		put_task_struct(p);
1150daf8e166STejun Heo 	}
1151daf8e166STejun Heo 	scx_task_iter_stop(&sti);
1152daf8e166STejun Heo 
11537c2cd767STejun Heo dump:
1154daf8e166STejun Heo 	scx_disable_dump(sch);
1155daf8e166STejun Heo 
1156daf8e166STejun Heo 	scx_cgroup_unlock();
1157daf8e166STejun Heo 	percpu_up_write(&scx_fork_rwsem);
1158daf8e166STejun Heo 
1159daf8e166STejun Heo 	/*
1160daf8e166STejun Heo 	 * All tasks are moved off of @sch but there may still be on-going
1161daf8e166STejun Heo 	 * operations (e.g. ops.select_cpu()). Drain them by flushing RCU. Use
1162daf8e166STejun Heo 	 * the expedited version as ancestors may be waiting in bypass mode.
1163daf8e166STejun Heo 	 * Also, tell the parent that there is no need to keep running bypass
1164daf8e166STejun Heo 	 * DSQs for us.
1165daf8e166STejun Heo 	 */
1166daf8e166STejun Heo 	synchronize_rcu_expedited();
1167daf8e166STejun Heo 	scx_disable_bypass_dsp(sch);
1168daf8e166STejun Heo 
1169daf8e166STejun Heo 	scx_unlink_sched(sch);
1170daf8e166STejun Heo 
1171daf8e166STejun Heo 	mutex_unlock(&scx_enable_mutex);
1172daf8e166STejun Heo 
1173daf8e166STejun Heo 	/*
1174daf8e166STejun Heo 	 * @sch is now unlinked from the parent's children list. Notify and call
1175daf8e166STejun Heo 	 * ops.sub_detach/exit(). Note that ops.sub_detach/exit() must be called
1176daf8e166STejun Heo 	 * after unlinking and releasing all locks. See scx_claim_exit().
1177daf8e166STejun Heo 	 */
1178daf8e166STejun Heo 	wake_up_all(&scx_unlink_waitq);
1179daf8e166STejun Heo 
1180daf8e166STejun Heo 	if (parent->ops.sub_detach && sch->sub_attached) {
1181daf8e166STejun Heo 		struct scx_sub_detach_args sub_detach_args = {
1182daf8e166STejun Heo 			.ops = &sch->ops,
1183daf8e166STejun Heo 			.cgroup_path = sch->cgrp_path,
1184daf8e166STejun Heo 		};
1185daf8e166STejun Heo 		SCX_CALL_OP(parent, sub_detach, NULL,
1186daf8e166STejun Heo 			    &sub_detach_args);
1187daf8e166STejun Heo 	}
1188daf8e166STejun Heo 
1189daf8e166STejun Heo 	scx_log_sched_disable(sch);
1190daf8e166STejun Heo 
1191daf8e166STejun Heo 	if (sch->ops.exit)
1192daf8e166STejun Heo 		SCX_CALL_OP(sch, exit, NULL, sch->exit_info);
119381507f14STejun Heo 
119481507f14STejun Heo 	/*
119581507f14STejun Heo 	 * @sch's non-ops programs such as timers and tracers can fire after
119681507f14STejun Heo 	 * ops.exit(). Now that exit is complete, stop scx_prog_sched() from
119781507f14STejun Heo 	 * resolving to @sch and drain in-flight resolvers.
119881507f14STejun Heo 	 */
119981507f14STejun Heo 	WRITE_ONCE(sch->dead, true);
120081507f14STejun Heo 	synchronize_rcu();
120181507f14STejun Heo 
1202daf8e166STejun Heo 	if (sch->sub_kset)
1203daf8e166STejun Heo 		kobject_del(&sch->sub_kset->kobj);
120480e6adaaSTejun Heo 	/* not added if enable failed before scx_sched_sysfs_add() */
120580e6adaaSTejun Heo 	if (sch->kobj.state_in_sysfs)
1206daf8e166STejun Heo 		kobject_del(&sch->kobj);
1207daf8e166STejun Heo }
1208daf8e166STejun Heo 
1209daf8e166STejun Heo /* verify that a scheduler can be attached to @cgrp and return the parent */
1210daf8e166STejun Heo static struct scx_sched *find_parent_sched(struct cgroup *cgrp)
1211daf8e166STejun Heo {
121279474420STejun Heo 	struct scx_sched *parent = scx_cgroup_sched(cgrp);
1213daf8e166STejun Heo 	struct scx_sched *pos;
1214daf8e166STejun Heo 
1215daf8e166STejun Heo 	lockdep_assert_held(&scx_sched_lock);
1216daf8e166STejun Heo 
1217daf8e166STejun Heo 	/* can't attach twice to the same cgroup */
1218daf8e166STejun Heo 	if (parent->cgrp == cgrp)
1219daf8e166STejun Heo 		return ERR_PTR(-EBUSY);
1220daf8e166STejun Heo 
1221daf8e166STejun Heo 	/* does $parent allow sub-scheds? */
1222daf8e166STejun Heo 	if (!parent->ops.sub_attach)
1223daf8e166STejun Heo 		return ERR_PTR(-EOPNOTSUPP);
1224daf8e166STejun Heo 
1225daf8e166STejun Heo 	/* can't insert between $parent and its exiting children */
1226daf8e166STejun Heo 	list_for_each_entry(pos, &parent->children, sibling)
1227daf8e166STejun Heo 		if (cgroup_is_descendant(pos->cgrp, cgrp))
1228daf8e166STejun Heo 			return ERR_PTR(-EBUSY);
1229daf8e166STejun Heo 
1230daf8e166STejun Heo 	return parent;
1231daf8e166STejun Heo }
1232daf8e166STejun Heo 
1233daf8e166STejun Heo static bool assert_task_ready_or_enabled(struct task_struct *p)
1234daf8e166STejun Heo {
1235daf8e166STejun Heo 	u32 state = scx_get_task_state(p);
1236daf8e166STejun Heo 
1237daf8e166STejun Heo 	switch (state) {
1238daf8e166STejun Heo 	case SCX_TASK_READY:
1239daf8e166STejun Heo 	case SCX_TASK_ENABLED:
1240daf8e166STejun Heo 		return true;
1241daf8e166STejun Heo 	default:
1242daf8e166STejun Heo 		WARN_ONCE(true, "sched_ext: Invalid task state %d for %s[%d] during enabling sub sched",
1243daf8e166STejun Heo 			  state, p->comm, p->pid);
1244daf8e166STejun Heo 		return false;
1245daf8e166STejun Heo 	}
1246daf8e166STejun Heo }
1247daf8e166STejun Heo 
1248daf8e166STejun Heo void scx_sub_enable_workfn(struct kthread_work *work)
1249daf8e166STejun Heo {
1250daf8e166STejun Heo 	struct scx_enable_cmd *cmd = container_of(work, struct scx_enable_cmd, work);
1251daf8e166STejun Heo 	struct sched_ext_ops *ops = cmd->ops;
1252daf8e166STejun Heo 	struct cgroup *cgrp;
1253daf8e166STejun Heo 	struct scx_sched *parent, *sch;
1254daf8e166STejun Heo 	struct scx_task_iter sti;
1255daf8e166STejun Heo 	struct task_struct *p;
1256daf8e166STejun Heo 	s32 i, ret;
1257daf8e166STejun Heo 
1258daf8e166STejun Heo 	mutex_lock(&scx_enable_mutex);
1259daf8e166STejun Heo 
1260daf8e166STejun Heo 	if (!scx_enabled()) {
1261daf8e166STejun Heo 		ret = -ENODEV;
1262daf8e166STejun Heo 		goto out_unlock;
1263daf8e166STejun Heo 	}
1264daf8e166STejun Heo 
1265daf8e166STejun Heo 	/* See scx_root_enable_workfn() for the @ops->priv check. */
1266daf8e166STejun Heo 	if (rcu_access_pointer(ops->priv)) {
1267daf8e166STejun Heo 		ret = -EBUSY;
1268daf8e166STejun Heo 		goto out_unlock;
1269daf8e166STejun Heo 	}
1270daf8e166STejun Heo 
1271daf8e166STejun Heo 	cgrp = cgroup_get_from_id(ops->sub_cgroup_id);
1272daf8e166STejun Heo 	if (IS_ERR(cgrp)) {
1273daf8e166STejun Heo 		ret = PTR_ERR(cgrp);
1274daf8e166STejun Heo 		goto out_unlock;
1275daf8e166STejun Heo 	}
1276daf8e166STejun Heo 
1277daf8e166STejun Heo 	raw_spin_lock_irq(&scx_sched_lock);
1278daf8e166STejun Heo 	parent = find_parent_sched(cgrp);
1279daf8e166STejun Heo 	if (IS_ERR(parent)) {
1280daf8e166STejun Heo 		raw_spin_unlock_irq(&scx_sched_lock);
1281daf8e166STejun Heo 		ret = PTR_ERR(parent);
1282daf8e166STejun Heo 		goto out_put_cgrp;
1283daf8e166STejun Heo 	}
1284daf8e166STejun Heo 	kobject_get(&parent->kobj);
1285daf8e166STejun Heo 	raw_spin_unlock_irq(&scx_sched_lock);
1286daf8e166STejun Heo 
12878946dbd3STejun Heo 	/*
12888946dbd3STejun Heo 	 * Flip the hot-path gates before ops->priv is published - the sub's
12898946dbd3STejun Heo 	 * programs can e.g. kick cpus from that point on. The matching dec is
12908946dbd3STejun Heo 	 * at the end of scx_sched_free_rcu_work().
12918946dbd3STejun Heo 	 */
12928946dbd3STejun Heo 	static_branch_inc(&__scx_has_subs);
12938946dbd3STejun Heo 
1294daf8e166STejun Heo 	/* scx_alloc_and_add_sched() consumes @cgrp whether it succeeds or not */
1295daf8e166STejun Heo 	sch = scx_alloc_and_add_sched(cmd, cgrp, parent);
1296daf8e166STejun Heo 	kobject_put(&parent->kobj);
1297daf8e166STejun Heo 	if (IS_ERR(sch)) {
12988946dbd3STejun Heo 		static_branch_dec(&__scx_has_subs);
1299daf8e166STejun Heo 		ret = PTR_ERR(sch);
1300daf8e166STejun Heo 		goto out_unlock;
1301daf8e166STejun Heo 	}
1302daf8e166STejun Heo 
130386094b95STejun Heo 	/*
130486094b95STejun Heo 	 * Validate before scx_link_sched() publishes @sch, so an invalid sub
130586094b95STejun Heo 	 * never becomes visible with an unallocated pshard.
130686094b95STejun Heo 	 */
130786094b95STejun Heo 	ret = scx_validate_ops(sch, ops);
130886094b95STejun Heo 	if (ret)
130986094b95STejun Heo 		goto err_disable;
131086094b95STejun Heo 
131186094b95STejun Heo 	/*
131286094b95STejun Heo 	 * Allocate pshard[] before scx_link_sched() publishes @sch into the
131386094b95STejun Heo 	 * parent's RCU children list. A concurrent revoke walking the tree
131486094b95STejun Heo 	 * would otherwise dereference sch->pshard[si] while it's still NULL.
131586094b95STejun Heo 	 * Unlike the root path, the cid shard layout is stable at this point.
131686094b95STejun Heo 	 *
131786094b95STejun Heo 	 * scx_alloc_pshards() skips allocation when @sch's arena pool isn't
131886094b95STejun Heo 	 * initialized, so scx_arena_pool_init() must run first.
131986094b95STejun Heo 	 */
132086094b95STejun Heo 	ret = scx_arena_pool_init(sch);
132186094b95STejun Heo 	if (ret)
132286094b95STejun Heo 		goto err_disable;
132386094b95STejun Heo 
132486094b95STejun Heo 	ret = scx_alloc_pshards(sch);
132586094b95STejun Heo 	if (ret)
132686094b95STejun Heo 		goto err_disable;
132786094b95STejun Heo 
1328daf8e166STejun Heo 	ret = scx_link_sched(sch);
1329daf8e166STejun Heo 	if (ret)
1330daf8e166STejun Heo 		goto err_disable;
1331daf8e166STejun Heo 
133280e6adaaSTejun Heo 	ret = scx_sched_sysfs_add(sch);
133380e6adaaSTejun Heo 	if (ret)
133480e6adaaSTejun Heo 		goto err_disable;
133580e6adaaSTejun Heo 
1336daf8e166STejun Heo 	if (sch->level >= SCX_SUB_MAX_DEPTH) {
1337daf8e166STejun Heo 		scx_error(sch, "max nesting depth %d violated",
1338daf8e166STejun Heo 			  SCX_SUB_MAX_DEPTH);
133900a08ddfSCui Jian 		ret = -EINVAL;
1340daf8e166STejun Heo 		goto err_disable;
1341daf8e166STejun Heo 	}
1342daf8e166STejun Heo 
1343daf8e166STejun Heo 	if (sch->ops.init) {
1344daf8e166STejun Heo 		ret = SCX_CALL_OP_RET(sch, init, NULL);
1345daf8e166STejun Heo 		if (ret) {
1346daf8e166STejun Heo 			ret = scx_ops_sanitize_err(sch, "init", ret);
1347daf8e166STejun Heo 			scx_error(sch, "ops.init() failed (%d)", ret);
1348daf8e166STejun Heo 			goto err_disable;
1349daf8e166STejun Heo 		}
1350daf8e166STejun Heo 		sch->exit_info->flags |= SCX_EFLAG_INITIALIZED;
1351daf8e166STejun Heo 	}
1352daf8e166STejun Heo 
1353daf8e166STejun Heo 	ret = scx_set_cmask_scratch_alloc(sch);
1354daf8e166STejun Heo 	if (ret)
1355daf8e166STejun Heo 		goto err_disable;
1356daf8e166STejun Heo 
1357daf8e166STejun Heo 	struct scx_sub_attach_args sub_attach_args = {
1358daf8e166STejun Heo 		.ops = &sch->ops,
1359daf8e166STejun Heo 		.cgroup_path = sch->cgrp_path,
1360daf8e166STejun Heo 	};
1361daf8e166STejun Heo 
1362daf8e166STejun Heo 	ret = SCX_CALL_OP_RET(parent, sub_attach, NULL,
1363daf8e166STejun Heo 			      &sub_attach_args);
1364daf8e166STejun Heo 	if (ret) {
1365daf8e166STejun Heo 		ret = scx_ops_sanitize_err(sch, "sub_attach", ret);
1366daf8e166STejun Heo 		scx_error(sch, "parent rejected (%d)", ret);
1367daf8e166STejun Heo 		goto err_disable;
1368daf8e166STejun Heo 	}
1369daf8e166STejun Heo 	sch->sub_attached = true;
1370daf8e166STejun Heo 
1371daf8e166STejun Heo 	scx_bypass(sch, true);
1372daf8e166STejun Heo 
1373daf8e166STejun Heo 	for (i = SCX_OPI_BEGIN; i < SCX_OPI_END; i++)
1374daf8e166STejun Heo 		if (((void (**)(void))ops)[i])
1375daf8e166STejun Heo 			set_bit(i, sch->has_op);
1376daf8e166STejun Heo 
1377daf8e166STejun Heo 	percpu_down_write(&scx_fork_rwsem);
1378daf8e166STejun Heo 	scx_cgroup_lock();
1379daf8e166STejun Heo 
1380daf8e166STejun Heo 	/*
1381daf8e166STejun Heo 	 * Set cgroup->scx_sched's and check CSS_ONLINE. Either we see
1382daf8e166STejun Heo 	 * !CSS_ONLINE or scx_cgroup_lifetime_notify() sees and shoots us down.
1383daf8e166STejun Heo 	 */
1384daf8e166STejun Heo 	set_cgroup_sched(sch_cgroup(sch), sch);
1385daf8e166STejun Heo 	if (!(cgrp->self.flags & CSS_ONLINE)) {
1386daf8e166STejun Heo 		scx_error(sch, "cgroup is not online");
138700a08ddfSCui Jian 		ret = -ENODEV;
1388daf8e166STejun Heo 		goto err_unlock_and_disable;
1389daf8e166STejun Heo 	}
1390daf8e166STejun Heo 
1391daf8e166STejun Heo 	/*
1392a6ec0b62STejun Heo 	 * Take over the subtree's cgroups before any task is claimed,
1393a6ec0b62STejun Heo 	 * mirroring root enable's cgroups-before-tasks order.
1394a6ec0b62STejun Heo 	 */
1395a6ec0b62STejun Heo 	ret = scx_cgroup_claim_subtree(sch);
1396a6ec0b62STejun Heo 	if (ret)
1397a6ec0b62STejun Heo 		goto err_unlock_and_disable;
1398a6ec0b62STejun Heo 
1399a6ec0b62STejun Heo 	/*
1400daf8e166STejun Heo 	 * Initialize tasks for the new child $sch without exiting them for
1401daf8e166STejun Heo 	 * $parent so that the tasks can always be reverted back to $parent
1402daf8e166STejun Heo 	 * sched on child init failure.
1403daf8e166STejun Heo 	 */
1404daf8e166STejun Heo 	WARN_ON_ONCE(scx_enabling_sub_sched);
1405daf8e166STejun Heo 	scx_enabling_sub_sched = sch;
1406daf8e166STejun Heo 
1407daf8e166STejun Heo 	scx_task_iter_start(&sti, sch->cgrp);
1408daf8e166STejun Heo 	while ((p = scx_task_iter_next_locked(&sti))) {
1409daf8e166STejun Heo 		struct rq *rq;
1410daf8e166STejun Heo 		struct rq_flags rf;
1411daf8e166STejun Heo 
1412daf8e166STejun Heo 		/*
1413daf8e166STejun Heo 		 * Task iteration may visit the same task twice when racing
1414daf8e166STejun Heo 		 * against exiting. Use %SCX_TASK_SUB_INIT to mark tasks which
1415daf8e166STejun Heo 		 * finished __scx_init_task() and skip if set.
1416daf8e166STejun Heo 		 *
1417daf8e166STejun Heo 		 * A task may exit and get freed between __scx_init_task()
1418daf8e166STejun Heo 		 * completion and scx_enable_task(). In such cases,
1419daf8e166STejun Heo 		 * scx_disable_and_exit_task() must exit the task for both the
1420daf8e166STejun Heo 		 * parent and child scheds.
1421daf8e166STejun Heo 		 */
1422daf8e166STejun Heo 		if (p->scx.flags & SCX_TASK_SUB_INIT)
1423daf8e166STejun Heo 			continue;
1424daf8e166STejun Heo 
1425daf8e166STejun Heo 		/* @p is pinned by the iter; see scx_sub_disable() */
1426daf8e166STejun Heo 		get_task_struct(p);
1427daf8e166STejun Heo 
1428daf8e166STejun Heo 		if (!assert_task_ready_or_enabled(p)) {
1429daf8e166STejun Heo 			ret = -EINVAL;
1430daf8e166STejun Heo 			goto abort;
1431daf8e166STejun Heo 		}
1432daf8e166STejun Heo 
1433daf8e166STejun Heo 		scx_task_iter_unlock(&sti);
1434daf8e166STejun Heo 
1435daf8e166STejun Heo 		/*
1436daf8e166STejun Heo 		 * As $p is still on $parent, it can't be transitioned to INIT.
1437daf8e166STejun Heo 		 * Let's worry about task state later. Use __scx_init_task().
1438daf8e166STejun Heo 		 */
1439bf9dee58STejun Heo 		ret = __scx_init_task(sch, p, NULL, false);
1440daf8e166STejun Heo 		if (ret)
1441daf8e166STejun Heo 			goto abort;
1442daf8e166STejun Heo 
1443daf8e166STejun Heo 		rq = task_rq_lock(p, &rf);
1444daf8e166STejun Heo 
1445daf8e166STejun Heo 		if (scx_get_task_state(p) == SCX_TASK_DEAD) {
1446daf8e166STejun Heo 			/*
1447daf8e166STejun Heo 			 * sched_ext_dead() raced us between __scx_init_task()
1448daf8e166STejun Heo 			 * and this rq lock and ran exit_task() on $parent (the
1449daf8e166STejun Heo 			 * sched @p was on at that point), not on @sch. @sch's
1450daf8e166STejun Heo 			 * just-completed init is owed an exit_task() and we
1451daf8e166STejun Heo 			 * issue it here.
1452daf8e166STejun Heo 			 */
1453daf8e166STejun Heo 			scx_sub_init_cancel_task(sch, p);
1454daf8e166STejun Heo 			task_rq_unlock(rq, p, &rf);
1455daf8e166STejun Heo 			put_task_struct(p);
1456daf8e166STejun Heo 			continue;
1457daf8e166STejun Heo 		}
1458daf8e166STejun Heo 
1459daf8e166STejun Heo 		p->scx.flags |= SCX_TASK_SUB_INIT;
1460daf8e166STejun Heo 		task_rq_unlock(rq, p, &rf);
1461daf8e166STejun Heo 
1462daf8e166STejun Heo 		put_task_struct(p);
1463daf8e166STejun Heo 	}
1464daf8e166STejun Heo 	scx_task_iter_stop(&sti);
1465daf8e166STejun Heo 
1466daf8e166STejun Heo 	/*
1467daf8e166STejun Heo 	 * All tasks are prepped. Disable/exit tasks for $parent and enable for
1468daf8e166STejun Heo 	 * the new @sch.
1469daf8e166STejun Heo 	 */
1470daf8e166STejun Heo 	scx_task_iter_start(&sti, sch->cgrp);
1471daf8e166STejun Heo 	while ((p = scx_task_iter_next_locked(&sti))) {
1472daf8e166STejun Heo 		/*
1473daf8e166STejun Heo 		 * Use clearing of %SCX_TASK_SUB_INIT to detect and skip
1474daf8e166STejun Heo 		 * duplicate iterations.
1475daf8e166STejun Heo 		 */
1476daf8e166STejun Heo 		if (!(p->scx.flags & SCX_TASK_SUB_INIT))
1477daf8e166STejun Heo 			continue;
1478daf8e166STejun Heo 
1479daf8e166STejun Heo 		scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE) {
1480daf8e166STejun Heo 			/*
1481daf8e166STejun Heo 			 * $p must be either READY or ENABLED. If ENABLED,
1482daf8e166STejun Heo 			 * __scx_disabled_and_exit_task() first disables and
1483daf8e166STejun Heo 			 * makes it READY. However, after exiting $p, it will
1484daf8e166STejun Heo 			 * leave $p as READY.
1485daf8e166STejun Heo 			 */
1486daf8e166STejun Heo 			assert_task_ready_or_enabled(p);
1487daf8e166STejun Heo 			__scx_disable_and_exit_task(parent, p);
1488daf8e166STejun Heo 
1489daf8e166STejun Heo 			/*
1490daf8e166STejun Heo 			 * $p is now only initialized for @sch and READY, which
14917c2cd767STejun Heo 			 * is what we want. Assign it to @sch and, if it's on
14927c2cd767STejun Heo 			 * the ext class, enable. A non-ext task, possible under
14937c2cd767STejun Heo 			 * an %SCX_OPS_SWITCH_PARTIAL root, stays READY and is
14947c2cd767STejun Heo 			 * enabled by switching_to_scx() if it switches over.
1495daf8e166STejun Heo 			 */
1496daf8e166STejun Heo 			scx_set_task_sched(p, sch);
14977c2cd767STejun Heo 			if (p->sched_class == &ext_sched_class)
1498daf8e166STejun Heo 				scx_enable_task(sch, p);
1499daf8e166STejun Heo 
1500daf8e166STejun Heo 			p->scx.flags &= ~SCX_TASK_SUB_INIT;
1501daf8e166STejun Heo 		}
1502daf8e166STejun Heo 	}
1503daf8e166STejun Heo 	scx_task_iter_stop(&sti);
1504daf8e166STejun Heo 
1505daf8e166STejun Heo 	scx_enabling_sub_sched = NULL;
1506daf8e166STejun Heo 
1507daf8e166STejun Heo 	scx_cgroup_unlock();
1508daf8e166STejun Heo 	percpu_up_write(&scx_fork_rwsem);
1509daf8e166STejun Heo 
1510daf8e166STejun Heo 	scx_bypass(sch, false);
1511daf8e166STejun Heo 
15125f2a9a4cSTejun Heo 	/* @sch is enabled; deliver any caps owed since its sub_attach() */
15135f2a9a4cSTejun Heo 	scx_sub_seed_caps(sch);
15145f2a9a4cSTejun Heo 
1515daf8e166STejun Heo 	pr_info("sched_ext: BPF sub-scheduler \"%s\" enabled\n", sch->ops.name);
1516daf8e166STejun Heo 	kobject_uevent(&sch->kobj, KOBJ_ADD);
1517daf8e166STejun Heo 	ret = 0;
1518daf8e166STejun Heo 	goto out_unlock;
1519daf8e166STejun Heo 
1520daf8e166STejun Heo out_put_cgrp:
1521daf8e166STejun Heo 	cgroup_put(cgrp);
1522daf8e166STejun Heo out_unlock:
1523daf8e166STejun Heo 	mutex_unlock(&scx_enable_mutex);
1524daf8e166STejun Heo 	cmd->ret = ret;
1525daf8e166STejun Heo 	return;
1526daf8e166STejun Heo 
1527daf8e166STejun Heo abort:
1528daf8e166STejun Heo 	put_task_struct(p);
1529daf8e166STejun Heo 	scx_task_iter_stop(&sti);
1530daf8e166STejun Heo 
1531daf8e166STejun Heo 	/*
1532daf8e166STejun Heo 	 * Undo __scx_init_task() for tasks we marked. scx_enable_task() never
1533daf8e166STejun Heo 	 * ran for @sch on them, so calling scx_disable_task() here would invoke
1534daf8e166STejun Heo 	 * ops.disable() without a matching ops.enable(). scx_enabling_sub_sched
1535daf8e166STejun Heo 	 * must stay set until SUB_INIT is cleared from every marked task -
1536daf8e166STejun Heo 	 * scx_disable_and_exit_task() reads it when a task exits concurrently.
1537daf8e166STejun Heo 	 */
1538daf8e166STejun Heo 	scx_task_iter_start(&sti, sch->cgrp);
1539daf8e166STejun Heo 	while ((p = scx_task_iter_next_locked(&sti))) {
1540daf8e166STejun Heo 		if (p->scx.flags & SCX_TASK_SUB_INIT) {
1541daf8e166STejun Heo 			scx_sub_init_cancel_task(sch, p);
1542daf8e166STejun Heo 			p->scx.flags &= ~SCX_TASK_SUB_INIT;
1543daf8e166STejun Heo 		}
1544daf8e166STejun Heo 	}
1545daf8e166STejun Heo 	scx_task_iter_stop(&sti);
1546daf8e166STejun Heo 	scx_enabling_sub_sched = NULL;
1547daf8e166STejun Heo err_unlock_and_disable:
1548daf8e166STejun Heo 	/* we'll soon enter disable path, keep bypass on */
1549daf8e166STejun Heo 	scx_cgroup_unlock();
1550daf8e166STejun Heo 	percpu_up_write(&scx_fork_rwsem);
1551daf8e166STejun Heo err_disable:
1552daf8e166STejun Heo 	mutex_unlock(&scx_enable_mutex);
1553ad45691dSTejun Heo 	/*
1554ad45691dSTejun Heo 	 * Some enable failures only return an errno (e.g. -ENOMEM from an
1555ad45691dSTejun Heo 	 * allocation) without calling scx_error(). Record it so
1556ad45691dSTejun Heo 	 * scx_flush_disable_work() runs the disable and ops.exit() fires.
1557ad45691dSTejun Heo 	 */
1558ad45691dSTejun Heo 	scx_error(sch, "scx_sub_enable() failed (%d)", ret);
1559daf8e166STejun Heo 	scx_flush_disable_work(sch);
1560daf8e166STejun Heo 	cmd->ret = 0;
1561daf8e166STejun Heo }
1562daf8e166STejun Heo 
1563bf9dee58STejun Heo /**
1564bf9dee58STejun Heo  * scx_cgroup_task_migrating - Prepare a task for a cgroup migration
1565bf9dee58STejun Heo  * @ctx: migration being prepared
1566bf9dee58STejun Heo  *
1567bf9dee58STejun Heo  * A task's sched must match its cgroup's owner, so a migration that crosses a
1568bf9dee58STejun Heo  * sched boundary re-homes the task once committed. Run the fallible part here,
1569bf9dee58STejun Heo  * before the migration commits: initialize the task for the destination sched.
1570bf9dee58STejun Heo  * A rejection fails the cgroup.procs write.
1571bf9dee58STejun Heo  */
1572bf9dee58STejun Heo static s32 scx_cgroup_task_migrating(struct cgroup_task_migrate_ctx *ctx)
1573bf9dee58STejun Heo {
1574bf9dee58STejun Heo 	struct task_struct *p = ctx->task;
1575bf9dee58STejun Heo 	struct scx_sched *to;
1576bf9dee58STejun Heo 	int ret;
1577bf9dee58STejun Heo 
1578bf9dee58STejun Heo 	/*
1579bf9dee58STejun Heo 	 * Cleared under scx_cgroup_lock() before root disable starts tearing
1580bf9dee58STejun Heo 	 * down tasks. As cgroup_mutex is held, a set flag guarantees that the
1581bf9dee58STejun Heo 	 * teardown loop is not running concurrently.
1582bf9dee58STejun Heo 	 */
1583bf9dee58STejun Heo 	if (!scx_cgroup_enabled)
1584bf9dee58STejun Heo 		return NOTIFY_OK;
1585bf9dee58STejun Heo 
158679474420STejun Heo 	to = scx_cgroup_sched(ctx->dst_dcgrp);
1587bf9dee58STejun Heo 	if (scx_task_on_sched(to, p))
1588bf9dee58STejun Heo 		return NOTIFY_OK;
1589bf9dee58STejun Heo 
1590bf9dee58STejun Heo 	ret = __scx_init_task(to, p, ctx->dst_dcgrp, false);
1591bf9dee58STejun Heo 	if (ret)
1592bf9dee58STejun Heo 		return notifier_from_errno(ret);
1593bf9dee58STejun Heo 
1594bf9dee58STejun Heo 	return NOTIFY_OK;
1595bf9dee58STejun Heo }
1596bf9dee58STejun Heo 
1597bf9dee58STejun Heo /**
1598bf9dee58STejun Heo  * scx_cgroup_task_migrated - Re-home a task that changed cgroups
1599bf9dee58STejun Heo  * @ctx: committed migration
1600bf9dee58STejun Heo  *
1601bf9dee58STejun Heo  * Move the task to its new cgroup's sched, which scx_cgroup_task_migrating()
1602bf9dee58STejun Heo  * already initialized it for. Can't fail.
1603bf9dee58STejun Heo  *
1604bf9dee58STejun Heo  * This is safe against all phases of the destination sched's destruction. A
1605bf9dee58STejun Heo  * disable resets cgroup ownership to the parent and re-homes tasks in one
1606bf9dee58STejun Heo  * scx_cgroup_lock() section. If that section already ran, the destination would
1607bf9dee58STejun Heo  * be the parent. Otherwise, the re-home loop is still ahead and guaranteed to
1608bf9dee58STejun Heo  * visit the task, now in the destination cgroup.
1609bf9dee58STejun Heo  */
1610bf9dee58STejun Heo static void scx_cgroup_task_migrated(struct cgroup_task_migrate_ctx *ctx)
1611bf9dee58STejun Heo {
1612bf9dee58STejun Heo 	struct task_struct *p = ctx->task;
1613bf9dee58STejun Heo 	struct scx_sched *to;
1614bf9dee58STejun Heo 	struct rq *rq;
1615bf9dee58STejun Heo 	struct rq_flags rf;
1616bf9dee58STejun Heo 
1617bf9dee58STejun Heo 	if (!scx_cgroup_enabled)
1618bf9dee58STejun Heo 		return;
1619bf9dee58STejun Heo 
162079474420STejun Heo 	to = scx_cgroup_sched(ctx->dst_dcgrp);
1621bf9dee58STejun Heo 	if (scx_task_on_sched(to, p))
1622bf9dee58STejun Heo 		return;
1623bf9dee58STejun Heo 
1624bf9dee58STejun Heo 	rq = task_rq_lock(p, &rf);
1625bf9dee58STejun Heo 	scx_rehome_task(to, p);
1626bf9dee58STejun Heo 	task_rq_unlock(rq, p, &rf);
1627bf9dee58STejun Heo }
1628bf9dee58STejun Heo 
1629bf9dee58STejun Heo /**
1630bf9dee58STejun Heo  * scx_cgroup_task_migrate_canceled - Undo migration preparation
1631bf9dee58STejun Heo  * @ctx: canceled migration
1632bf9dee58STejun Heo  *
1633bf9dee58STejun Heo  * The migration failed after scx_cgroup_task_migrating() initialized the task
1634bf9dee58STejun Heo  * for the destination sched. The task stays on its current sched in the source
1635bf9dee58STejun Heo  * cgroup. Undo the destination's init.
1636bf9dee58STejun Heo  */
1637bf9dee58STejun Heo static void scx_cgroup_task_migrate_canceled(struct cgroup_task_migrate_ctx *ctx)
1638bf9dee58STejun Heo {
1639bf9dee58STejun Heo 	struct task_struct *p = ctx->task;
1640bf9dee58STejun Heo 	struct scx_sched *to;
1641bf9dee58STejun Heo 	struct rq *rq;
1642bf9dee58STejun Heo 	struct rq_flags rf;
1643bf9dee58STejun Heo 
1644bf9dee58STejun Heo 	if (!scx_cgroup_enabled)
1645bf9dee58STejun Heo 		return;
1646bf9dee58STejun Heo 
164779474420STejun Heo 	to = scx_cgroup_sched(ctx->dst_dcgrp);
1648bf9dee58STejun Heo 	if (scx_task_on_sched(to, p))
1649bf9dee58STejun Heo 		return;
1650bf9dee58STejun Heo 
1651bf9dee58STejun Heo 	rq = task_rq_lock(p, &rf);
1652bf9dee58STejun Heo 	scx_sub_init_cancel_task(to, p);
1653bf9dee58STejun Heo 	task_rq_unlock(rq, p, &rf);
1654bf9dee58STejun Heo }
1655bf9dee58STejun Heo 
1656daf8e166STejun Heo static s32 scx_cgroup_lifetime_notify(struct notifier_block *nb,
1657daf8e166STejun Heo 				      unsigned long action, void *data)
1658daf8e166STejun Heo {
1659daf8e166STejun Heo 	struct cgroup *cgrp = data;
1660daf8e166STejun Heo 	struct cgroup *parent = cgroup_parent(cgrp);
166179474420STejun Heo 	struct scx_sched *sch;
1662daf8e166STejun Heo 
1663daf8e166STejun Heo 	if (!cgroup_on_dfl(cgrp))
1664daf8e166STejun Heo 		return NOTIFY_OK;
1665daf8e166STejun Heo 
1666daf8e166STejun Heo 	switch (action) {
1667daf8e166STejun Heo 	case CGROUP_LIFETIME_ONLINE:
1668daf8e166STejun Heo 		/* inherit ->scx_sched from $parent */
1669daf8e166STejun Heo 		if (parent)
167079474420STejun Heo 			rcu_assign_pointer(cgrp->scx_sched, scx_cgroup_sched(parent));
1671daf8e166STejun Heo 		break;
1672daf8e166STejun Heo 	case CGROUP_LIFETIME_OFFLINE:
1673daf8e166STejun Heo 		/* if there is a sched attached, shoot it down */
167479474420STejun Heo 		sch = scx_cgroup_sched(cgrp);
167579474420STejun Heo 		if (sch && sch->cgrp == cgrp)
167679474420STejun Heo 			scx_exit(sch, SCX_EXIT_UNREG_KERN,
1677daf8e166STejun Heo 				 SCX_ECODE_RSN_CGROUP_OFFLINE,
1678daf8e166STejun Heo 				 "cgroup %llu going offline", cgroup_id(cgrp));
1679daf8e166STejun Heo 		break;
1680daf8e166STejun Heo 	}
1681daf8e166STejun Heo 
1682daf8e166STejun Heo 	return NOTIFY_OK;
1683daf8e166STejun Heo }
1684daf8e166STejun Heo 
1685daf8e166STejun Heo static struct notifier_block scx_cgroup_lifetime_nb = {
1686daf8e166STejun Heo 	.notifier_call = scx_cgroup_lifetime_notify,
1687daf8e166STejun Heo };
1688daf8e166STejun Heo 
1689bf9dee58STejun Heo static s32 scx_cgroup_task_notify(struct notifier_block *nb,
1690bf9dee58STejun Heo 				  unsigned long action, void *data)
1691daf8e166STejun Heo {
1692bf9dee58STejun Heo 	struct cgroup_task_migrate_ctx *ctx = data;
1693bf9dee58STejun Heo 
1694bf9dee58STejun Heo 	switch (action) {
1695bf9dee58STejun Heo 	case CGROUP_TASK_MIGRATING:
1696bf9dee58STejun Heo 		return scx_cgroup_task_migrating(ctx);
1697bf9dee58STejun Heo 	case CGROUP_TASK_MIGRATED:
1698bf9dee58STejun Heo 		scx_cgroup_task_migrated(ctx);
1699bf9dee58STejun Heo 		break;
1700bf9dee58STejun Heo 	case CGROUP_TASK_MIGRATE_CANCELED:
1701bf9dee58STejun Heo 		scx_cgroup_task_migrate_canceled(ctx);
1702bf9dee58STejun Heo 		break;
1703daf8e166STejun Heo 	}
1704bf9dee58STejun Heo 
1705bf9dee58STejun Heo 	return NOTIFY_OK;
1706bf9dee58STejun Heo }
1707bf9dee58STejun Heo 
1708bf9dee58STejun Heo static struct notifier_block scx_cgroup_task_nb = {
1709bf9dee58STejun Heo 	.notifier_call = scx_cgroup_task_notify,
1710bf9dee58STejun Heo };
1711bf9dee58STejun Heo 
1712bf9dee58STejun Heo static s32 __init scx_cgroup_notifier_init(void)
1713bf9dee58STejun Heo {
1714bf9dee58STejun Heo 	s32 ret;
1715bf9dee58STejun Heo 
1716bf9dee58STejun Heo 	ret = blocking_notifier_chain_register(&cgroup_lifetime_notifier,
1717bf9dee58STejun Heo 					       &scx_cgroup_lifetime_nb);
1718bf9dee58STejun Heo 	if (ret)
1719bf9dee58STejun Heo 		return ret;
1720bf9dee58STejun Heo 
1721bf9dee58STejun Heo 	return blocking_notifier_chain_register(&cgroup_task_notifier,
1722bf9dee58STejun Heo 						&scx_cgroup_task_nb);
1723bf9dee58STejun Heo }
1724bf9dee58STejun Heo core_initcall(scx_cgroup_notifier_init);
1725daf8e166STejun Heo 
17265f2a9a4cSTejun Heo static void scx_pstack_recursion(struct bpf_prog *prog, const char *op)
1727daf8e166STejun Heo {
1728daf8e166STejun Heo 	struct scx_sched *sch;
1729daf8e166STejun Heo 
1730daf8e166STejun Heo 	guard(rcu)();
1731daf8e166STejun Heo 	sch = scx_prog_sched(prog->aux);
1732daf8e166STejun Heo 	if (unlikely(!sch))
1733daf8e166STejun Heo 		return;
1734daf8e166STejun Heo 
17355f2a9a4cSTejun Heo 	scx_error(sch, "%s recursion detected", op);
17365f2a9a4cSTejun Heo }
17375f2a9a4cSTejun Heo 
17385f2a9a4cSTejun Heo void scx_pstack_recursion_on_dispatch(struct bpf_prog *prog)
17395f2a9a4cSTejun Heo {
17405f2a9a4cSTejun Heo 	scx_pstack_recursion(prog, "dispatch");
17415f2a9a4cSTejun Heo }
17425f2a9a4cSTejun Heo 
17435f2a9a4cSTejun Heo void scx_pstack_recursion_on_caps_updated(struct bpf_prog *prog)
17445f2a9a4cSTejun Heo {
17455f2a9a4cSTejun Heo 	scx_pstack_recursion(prog, "sub_caps_updated");
1746daf8e166STejun Heo }
1747daf8e166STejun Heo 
1748daf8e166STejun Heo __bpf_kfunc_start_defs();
1749daf8e166STejun Heo 
1750daf8e166STejun Heo /**
1751daf8e166STejun Heo  * scx_bpf_sub_dispatch - Trigger dispatching on a child scheduler
1752daf8e166STejun Heo  * @cgroup_id: cgroup ID of the child scheduler to dispatch
1753daf8e166STejun Heo  * @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
1754daf8e166STejun Heo  *
1755daf8e166STejun Heo  * Allows a parent scheduler to trigger dispatching on one of its direct
1756daf8e166STejun Heo  * child schedulers. The child scheduler runs its dispatch operation to
1757daf8e166STejun Heo  * move tasks from dispatch queues to the local runqueue.
1758daf8e166STejun Heo  *
1759daf8e166STejun Heo  * Returns: true on success, false if cgroup_id is invalid, not a direct
1760daf8e166STejun Heo  * child, or caller lacks dispatch permission.
1761daf8e166STejun Heo  */
1762daf8e166STejun Heo __bpf_kfunc bool scx_bpf_sub_dispatch(u64 cgroup_id, const struct bpf_prog_aux *aux)
1763daf8e166STejun Heo {
1764daf8e166STejun Heo 	struct rq *this_rq = this_rq();
1765daf8e166STejun Heo 	struct scx_sched *parent, *child;
1766daf8e166STejun Heo 
1767daf8e166STejun Heo 	guard(rcu)();
1768daf8e166STejun Heo 	parent = scx_prog_sched(aux);
1769daf8e166STejun Heo 	if (unlikely(!parent))
1770daf8e166STejun Heo 		return false;
1771daf8e166STejun Heo 
1772daf8e166STejun Heo 	child = scx_find_sub_sched(cgroup_id);
1773daf8e166STejun Heo 
1774daf8e166STejun Heo 	if (unlikely(!child))
1775daf8e166STejun Heo 		return false;
1776daf8e166STejun Heo 
1777daf8e166STejun Heo 	if (unlikely(scx_parent(child) != parent)) {
1778daf8e166STejun Heo 		scx_error(parent, "trying to dispatch a distant sub-sched on cgroup %llu",
1779daf8e166STejun Heo 			  cgroup_id);
1780daf8e166STejun Heo 		return false;
1781daf8e166STejun Heo 	}
1782daf8e166STejun Heo 
1783147d1885STejun Heo 	/*
1784147d1885STejun Heo 	 * Skip a child that does not effectively hold the base cap on this cpu:
1785147d1885STejun Heo 	 * its inserts would only be rejected. ecaps are synced at the top of
1786147d1885STejun Heo 	 * balance_one() before dispatch, so this reflects the in-effect state.
1787147d1885STejun Heo 	 */
1788147d1885STejun Heo 	if (scx_missing_caps(child, cpu_of(this_rq), SCX_CAP_BASE))
1789147d1885STejun Heo 		return false;
1790147d1885STejun Heo 
1791daf8e166STejun Heo 	return scx_dispatch_sched(child, this_rq, this_rq->scx.sub_dispatch_prev,
1792daf8e166STejun Heo 				  true);
1793daf8e166STejun Heo }
1794daf8e166STejun Heo 
179586094b95STejun Heo /* Validate common inputs. On success, *parent_out and *child_out are set. */
179686094b95STejun Heo static s32 sub_cap_preamble(u64 cgroup_id, u64 caps, const struct bpf_prog_aux *aux,
179786094b95STejun Heo 			    struct scx_sched **parent_out, struct scx_sched **child_out)
179886094b95STejun Heo {
179986094b95STejun Heo 	struct scx_sched *parent, *child;
180086094b95STejun Heo 
180186094b95STejun Heo 	parent = scx_prog_sched(aux);
180286094b95STejun Heo 	if (unlikely(!parent))
180386094b95STejun Heo 		return -ENODEV;
180486094b95STejun Heo 
180586094b95STejun Heo 	if (!scx_is_cid_type()) {
180686094b95STejun Heo 		scx_error(parent, "sub-cap kfuncs require a cid-form scheduler");
180786094b95STejun Heo 		return -EOPNOTSUPP;
180886094b95STejun Heo 	}
180986094b95STejun Heo 
181086094b95STejun Heo 	child = scx_find_sub_sched(cgroup_id);
181186094b95STejun Heo 	if (unlikely(!child))
181286094b95STejun Heo 		return -ENODEV;
181386094b95STejun Heo 
181486094b95STejun Heo 	if (unlikely(scx_parent(child) != parent)) {
181586094b95STejun Heo 		scx_error(parent, "%s: sub-%llu is not a direct child",
181686094b95STejun Heo 			  parent->cgrp_path, cgroup_id);
181786094b95STejun Heo 		return -EINVAL;
181886094b95STejun Heo 	}
181986094b95STejun Heo 
182086094b95STejun Heo 	if (unlikely(caps & ~__SCX_CAP_ALL)) {
182186094b95STejun Heo 		scx_error(parent, "invalid caps 0x%llx", caps);
182286094b95STejun Heo 		return -EINVAL;
182386094b95STejun Heo 	}
182486094b95STejun Heo 
182586094b95STejun Heo 	*parent_out = parent;
182686094b95STejun Heo 	*child_out = child;
182786094b95STejun Heo 	return 0;
182886094b95STejun Heo }
182986094b95STejun Heo 
183086094b95STejun Heo /**
183186094b95STejun Heo  * scx_bpf_sub_grant - Grant @caps on @cmask__ign's cids to a direct child
183286094b95STejun Heo  * @cgroup_id: cgroup id of the direct child sub-sched
183386094b95STejun Heo  * @caps: bitmask of SCX_CAP_* to grant
183486094b95STejun Heo  * @cmask__ign: cid cmask to grant @caps on (arena pointer)
183586094b95STejun Heo  * @denied_out__ign: optional arena cmask accumulating refused cids
183686094b95STejun Heo  * @aux: implicit BPF argument
183786094b95STejun Heo  *
183886094b95STejun Heo  * A cid in @cmask__ign is granted to the child only if the parent holds every
183986094b95STejun Heo  * requested cap on it. Refused cids are OR'd into @denied_out__ign when
184086094b95STejun Heo  * provided. Refusals outside @denied_out__ign's range are not recorded.
184186094b95STejun Heo  *
184286094b95STejun Heo  * All-or-nothing keeps the caller-visible result binary per cid, so
184386094b95STejun Heo  * @denied_out__ign is one mask to interpret rather than a per-cap matrix.
184486094b95STejun Heo  *
184586094b95STejun Heo  * Return 0 on full success, -EPERM if any cid was refused, or a negative
184686094b95STejun Heo  * errno on other failures.
184786094b95STejun Heo  */
184886094b95STejun Heo __bpf_kfunc s32 scx_bpf_sub_grant(u64 cgroup_id, u64 caps,
184986094b95STejun Heo 				  const struct scx_cmask *cmask__ign,
185086094b95STejun Heo 				  struct scx_cmask *denied_out__ign,
185186094b95STejun Heo 				  const struct bpf_prog_aux *aux)
185286094b95STejun Heo {
185386094b95STejun Heo 	struct scx_cmask_ref ref, denied_ref;
185486094b95STejun Heo 	struct scx_sched *parent, *child;
185586094b95STejun Heo 	bool any_denied = false;
18565f2a9a4cSTejun Heo 	LIST_HEAD(to_deliver);
185786094b95STejun Heo 	s32 si, ret;
185886094b95STejun Heo 
185986094b95STejun Heo 	guard(irqsave)();
186086094b95STejun Heo 
186186094b95STejun Heo 	ret = sub_cap_preamble(cgroup_id, caps, aux, &parent, &child);
186286094b95STejun Heo 	if (ret)
186386094b95STejun Heo 		return ret;
186486094b95STejun Heo 
186586094b95STejun Heo 	ret = scx_cmask_ref_init(parent, cmask__ign, &ref);
186686094b95STejun Heo 	if (ret) {
186786094b95STejun Heo 		scx_error(parent, "invalid cmask (%d)", ret);
186886094b95STejun Heo 		return ret;
186986094b95STejun Heo 	}
187086094b95STejun Heo 
187186094b95STejun Heo 	if (denied_out__ign) {
187286094b95STejun Heo 		ret = scx_cmask_ref_init(parent, denied_out__ign, &denied_ref);
187386094b95STejun Heo 		if (ret) {
187486094b95STejun Heo 			scx_error(parent, "invalid denied_out (%d)", ret);
187586094b95STejun Heo 			return ret;
187686094b95STejun Heo 		}
187786094b95STejun Heo 	}
187886094b95STejun Heo 
187986094b95STejun Heo 	/* apply the grant one shard at a time */
188086094b95STejun Heo 	for (si = ref.shard_first; si < ref.shard_end; si++) {
188186094b95STejun Heo 		SCX_CMASK_DEFINE_SHARD(slice, 0, SCX_CID_SHARD_MAX_CPUS);
188286094b95STejun Heo 		struct scx_pshard *pps = parent->pshard[si];
188386094b95STejun Heo 		struct scx_pshard *cps = child->pshard[si];
18845f2a9a4cSTejun Heo 		u64 granted_caps = 0;
188586094b95STejun Heo 		u32 cap_bit;
188686094b95STejun Heo 
188786094b95STejun Heo 		scx_cmask_ref_shard(&ref, si, slice);
188886094b95STejun Heo 		if (scx_cmask_empty(slice))
188986094b95STejun Heo 			continue;
189086094b95STejun Heo 
189186094b95STejun Heo 		SCX_CMASK_DEFINE_SHARD(granted_cids, slice->base, slice->nr_cids);
18925f2a9a4cSTejun Heo 		SCX_CMASK_DEFINE_SHARD(changed_cids, slice->base, slice->nr_cids);
18935f2a9a4cSTejun Heo 		SCX_CMASK_DEFINE_SHARD(delta, slice->base, slice->nr_cids);
18945f2a9a4cSTejun Heo 
189586094b95STejun Heo 		scx_cmask_copy(granted_cids, slice);
189686094b95STejun Heo 
189786094b95STejun Heo 		scoped_guard (raw_spinlock, &pps->lock) {
189886094b95STejun Heo 			guard(raw_spinlock_nested)(&cps->lock);
189986094b95STejun Heo 
190086094b95STejun Heo 			/*
190186094b95STejun Heo 			 * Narrow granted_cids to cids the parent holds every
190286094b95STejun Heo 			 * requested cap on. All-or-nothing per cid.
190386094b95STejun Heo 			 */
190486094b95STejun Heo 			scx_for_each_cap_bit(cap_bit, caps)
190586094b95STejun Heo 				scx_cmask_and(granted_cids, &pps->caps[cap_bit].cmask);
190686094b95STejun Heo 
19075f2a9a4cSTejun Heo 			/*
19085f2a9a4cSTejun Heo 			 * For each requested cap, fold the newly-set cids into
19095f2a9a4cSTejun Heo 			 * the child and accumulate the delta.
19105f2a9a4cSTejun Heo 			 */
19115f2a9a4cSTejun Heo 			scx_for_each_cap_bit(cap_bit, caps) {
19125f2a9a4cSTejun Heo 				struct scx_cmask *ccm = &cps->caps[cap_bit].cmask;
19135f2a9a4cSTejun Heo 
19145f2a9a4cSTejun Heo 				scx_cmask_copy(delta, granted_cids);
19155f2a9a4cSTejun Heo 				scx_cmask_andnot(delta, ccm);
19165f2a9a4cSTejun Heo 				if (scx_cmask_empty(delta))
19175f2a9a4cSTejun Heo 					continue;
19185f2a9a4cSTejun Heo 
19195f2a9a4cSTejun Heo 				scx_cmask_or(ccm, delta);
19205f2a9a4cSTejun Heo 				scx_cmask_or(changed_cids, delta);
19215f2a9a4cSTejun Heo 				granted_caps |= BIT_U64(cap_bit);
19225f2a9a4cSTejun Heo 			}
19235f2a9a4cSTejun Heo 
192456fdc35bSTejun Heo 			if (granted_caps) {
192556fdc35bSTejun Heo 				s32 cid;
192656fdc35bSTejun Heo 
19275f2a9a4cSTejun Heo 				caps_updated_record(cps, changed_cids, granted_caps,
19285f2a9a4cSTejun Heo 						    &to_deliver);
1929ca3aec45STejun Heo 				/*
1930ca3aec45STejun Heo 				 * The sync arms an update_idle() re-notify if
1931ca3aec45STejun Heo 				 * the cid gains baseline access, so the holder
1932ca3aec45STejun Heo 				 * learns of an already-idle cid.
1933ca3aec45STejun Heo 				 */
193456fdc35bSTejun Heo 				scx_cmask_for_each_cid(cid, changed_cids)
193556fdc35bSTejun Heo 					queue_sync_ecaps(child, cid);
193656fdc35bSTejun Heo 			}
193786094b95STejun Heo 		}
193886094b95STejun Heo 
193986094b95STejun Heo 		/* record cids that didn't make it through into @denied_out */
194086094b95STejun Heo 		if (!scx_cmask_subset(slice, granted_cids)) {
194186094b95STejun Heo 			any_denied = true;
194286094b95STejun Heo 			if (denied_out__ign) {
194386094b95STejun Heo 				SCX_CMASK_DEFINE_SHARD(denied, slice->base, slice->nr_cids);
194486094b95STejun Heo 
194586094b95STejun Heo 				scx_cmask_copy(denied, slice);
194686094b95STejun Heo 				scx_cmask_andnot(denied, granted_cids);
194786094b95STejun Heo 				scx_cmask_ref_or(&denied_ref, denied);
194886094b95STejun Heo 			}
194986094b95STejun Heo 		}
195086094b95STejun Heo 	}
19515f2a9a4cSTejun Heo 
19525f2a9a4cSTejun Heo 	caps_updated_deliver(&to_deliver);
19535f2a9a4cSTejun Heo 
195486094b95STejun Heo 	return any_denied ? -EPERM : 0;
195586094b95STejun Heo }
195686094b95STejun Heo 
195786094b95STejun Heo /**
195886094b95STejun Heo  * scx_bpf_sub_revoke - Revoke @caps on @cmask__ign's cids from @child
195986094b95STejun Heo  * @cgroup_id: cgroup id of the direct child sub-sched
196086094b95STejun Heo  * @caps: bitmask of SCX_CAP_* to revoke
196186094b95STejun Heo  * @cmask__ign: cid cmask to revoke @caps on (arena pointer)
196286094b95STejun Heo  * @aux: implicit BPF argument
196386094b95STejun Heo  *
196486094b95STejun Heo  * Clear @caps bits on @cmask__ign from the child named by @cgroup_id and all
196586094b95STejun Heo  * its descendants. The origin parent's pshard lock is held across the subtree
196686094b95STejun Heo  * walk so a concurrent grant from the origin parent observes the revoked
196786094b95STejun Heo  * state.
196886094b95STejun Heo  */
196986094b95STejun Heo __bpf_kfunc void scx_bpf_sub_revoke(u64 cgroup_id, u64 caps,
197086094b95STejun Heo 				    const struct scx_cmask *cmask__ign,
197186094b95STejun Heo 				    const struct bpf_prog_aux *aux)
197286094b95STejun Heo {
197386094b95STejun Heo 	struct scx_cmask_ref ref;
197486094b95STejun Heo 	struct scx_sched *parent, *child, *pos;
19755f2a9a4cSTejun Heo 	LIST_HEAD(to_deliver);
197686094b95STejun Heo 	s32 si, ret;
197786094b95STejun Heo 
197886094b95STejun Heo 	guard(irqsave)();
197986094b95STejun Heo 
198086094b95STejun Heo 	if (sub_cap_preamble(cgroup_id, caps, aux, &parent, &child))
198186094b95STejun Heo 		return;
198286094b95STejun Heo 
198386094b95STejun Heo 	ret = scx_cmask_ref_init(parent, cmask__ign, &ref);
198486094b95STejun Heo 	if (ret) {
198586094b95STejun Heo 		scx_error(parent, "invalid cmask (%d)", ret);
198686094b95STejun Heo 		return;
198786094b95STejun Heo 	}
198886094b95STejun Heo 
198986094b95STejun Heo 	/* per-shard, walk child's subtree and clear @caps */
199086094b95STejun Heo 	for (si = ref.shard_first; si < ref.shard_end; si++) {
199186094b95STejun Heo 		SCX_CMASK_DEFINE_SHARD(slice, 0, SCX_CID_SHARD_MAX_CPUS);
199286094b95STejun Heo 
199386094b95STejun Heo 		scx_cmask_ref_shard(&ref, si, slice);
199486094b95STejun Heo 		if (scx_cmask_empty(slice))
199586094b95STejun Heo 			continue;
199686094b95STejun Heo 
199786094b95STejun Heo 		/*
199886094b95STejun Heo 		 * Pre-order with subtree skip: a descendant that cleared
199986094b95STejun Heo 		 * nothing means no descendant of it can hold @caps on these
200086094b95STejun Heo 		 * cids either.
200186094b95STejun Heo 		 */
200286094b95STejun Heo 		guard(raw_spinlock)(&parent->pshard[si]->lock);
200386094b95STejun Heo 		pos = scx_next_descendant_pre(NULL, child);
200486094b95STejun Heo 		while (pos) {
200586094b95STejun Heo 			struct scx_pshard *ps = pos->pshard[si];
20065f2a9a4cSTejun Heo 			SCX_CMASK_DEFINE_SHARD(changed_cids, slice->base, slice->nr_cids);
20075f2a9a4cSTejun Heo 			SCX_CMASK_DEFINE_SHARD(delta, slice->base, slice->nr_cids);
200886094b95STejun Heo 			u64 revoked_caps = 0;
200986094b95STejun Heo 			u32 cap_bit;
201086094b95STejun Heo 
201186094b95STejun Heo 			scoped_guard (raw_spinlock_nested, &ps->lock) {
20125f2a9a4cSTejun Heo 				/*
20135f2a9a4cSTejun Heo 				 * For each cap, clear lost cids and accumulate
20145f2a9a4cSTejun Heo 				 * the per-cap diff for notification.
20155f2a9a4cSTejun Heo 				 */
201686094b95STejun Heo 				scx_for_each_cap_bit(cap_bit, caps) {
201786094b95STejun Heo 					struct scx_cmask *cm = &ps->caps[cap_bit].cmask;
201886094b95STejun Heo 
20195f2a9a4cSTejun Heo 					scx_cmask_copy(delta, cm);
20205f2a9a4cSTejun Heo 					scx_cmask_and(delta, slice);
20215f2a9a4cSTejun Heo 					if (scx_cmask_empty(delta))
202286094b95STejun Heo 						continue;
20235f2a9a4cSTejun Heo 
20245f2a9a4cSTejun Heo 					scx_cmask_andnot(cm, delta);
20255f2a9a4cSTejun Heo 					scx_cmask_or(changed_cids, delta);
202686094b95STejun Heo 					revoked_caps |= BIT_U64(cap_bit);
202786094b95STejun Heo 				}
20285f2a9a4cSTejun Heo 
202956fdc35bSTejun Heo 				if (revoked_caps) {
203056fdc35bSTejun Heo 					s32 cid;
203156fdc35bSTejun Heo 
20325f2a9a4cSTejun Heo 					caps_updated_record(ps, changed_cids, revoked_caps,
20335f2a9a4cSTejun Heo 							    &to_deliver);
203456fdc35bSTejun Heo 					scx_cmask_for_each_cid(cid, changed_cids)
203556fdc35bSTejun Heo 						queue_sync_ecaps(pos, cid);
203656fdc35bSTejun Heo 				}
203786094b95STejun Heo 			}
203886094b95STejun Heo 
203986094b95STejun Heo 			if (revoked_caps)
204086094b95STejun Heo 				pos = scx_next_descendant_pre(pos, child);
204186094b95STejun Heo 			else
204286094b95STejun Heo 				pos = scx_skip_subtree_pre(pos, child);
204386094b95STejun Heo 		}
204486094b95STejun Heo 	}
20455f2a9a4cSTejun Heo 
20465f2a9a4cSTejun Heo 	caps_updated_deliver(&to_deliver);
204786094b95STejun Heo }
204886094b95STejun Heo 
204986094b95STejun Heo /**
205086094b95STejun Heo  * scx_bpf_sub_caps - Read self's or a direct child's cap cmasks
205186094b95STejun Heo  * @cgroup_id: 0 for self, or a direct child's cgroup id
205286094b95STejun Heo  * @caps: one or more SCX_CAP_* bits
205386094b95STejun Heo  * @out__ign: arena cmask to receive the union of @caps within its range
205486094b95STejun Heo  * @aux: implicit BPF argument
205586094b95STejun Heo  *
205686094b95STejun Heo  * Read the cap cmasks granted on each cid for self (@cgroup_id 0) or a direct
205786094b95STejun Heo  * child - the literal granted set. A sched can read only itself or a direct
205886094b95STejun Heo  * child.
205986094b95STejun Heo  *
206086094b95STejun Heo  * Return 0, -ENODEV if @cgroup_id names no direct child, or -EINVAL on bad
206186094b95STejun Heo  * inputs.
206286094b95STejun Heo  */
206386094b95STejun Heo __bpf_kfunc s32 scx_bpf_sub_caps(u64 cgroup_id, u64 caps, struct scx_cmask *out__ign,
206486094b95STejun Heo 				 const struct bpf_prog_aux *aux)
206586094b95STejun Heo {
206686094b95STejun Heo 	struct scx_cmask_ref ref;
206786094b95STejun Heo 	struct scx_sched *sch, *target;
206886094b95STejun Heo 	struct scx_pshard **pshard;
206986094b95STejun Heo 	s32 si, ret;
207086094b95STejun Heo 
207186094b95STejun Heo 	guard(irqsave)();
207286094b95STejun Heo 
207386094b95STejun Heo 	sch = scx_prog_sched(aux);
207486094b95STejun Heo 	if (unlikely(!sch))
207586094b95STejun Heo 		return -ENODEV;
207686094b95STejun Heo 
207786094b95STejun Heo 	if (!scx_is_cid_type()) {
207886094b95STejun Heo 		scx_error(sch, "sub-cap kfuncs require a cid-form scheduler");
207986094b95STejun Heo 		return -EOPNOTSUPP;
208086094b95STejun Heo 	}
208186094b95STejun Heo 
208286094b95STejun Heo 	if (unlikely(caps & ~__SCX_CAP_ALL)) {
208386094b95STejun Heo 		scx_error(sch, "invalid caps 0x%llx", caps);
208486094b95STejun Heo 		return -EINVAL;
208586094b95STejun Heo 	}
208686094b95STejun Heo 
208786094b95STejun Heo 	/* @cgroup_id 0 reads self, otherwise a direct child */
208886094b95STejun Heo 	if (cgroup_id) {
208986094b95STejun Heo 		target = scx_find_sub_sched(cgroup_id);
209086094b95STejun Heo 		if (unlikely(!target))
209186094b95STejun Heo 			return -ENODEV;
209286094b95STejun Heo 		if (unlikely(scx_parent(target) != sch)) {
209386094b95STejun Heo 			scx_error(sch, "%s: sub-%llu is not a direct child",
209486094b95STejun Heo 				  sch->cgrp_path, cgroup_id);
209586094b95STejun Heo 			return -EINVAL;
209686094b95STejun Heo 		}
209786094b95STejun Heo 	} else {
209886094b95STejun Heo 		target = sch;
209986094b95STejun Heo 	}
210086094b95STejun Heo 
210186094b95STejun Heo 	/*
210286094b95STejun Heo 	 * The target's caps storage may not be set up yet (e.g. a self-read
210386094b95STejun Heo 	 * during ops.init_cids()). Pairs with the publish in
21043a773220STejun Heo 	 * scx_alloc_pshards(): a non-NULL pshard has every element set and the
21053a773220STejun Heo 	 * acquire also orders the cid table reads below against it.
210686094b95STejun Heo 	 */
21073a773220STejun Heo 	pshard = smp_load_acquire(&target->pshard);
210886094b95STejun Heo 	if (unlikely(!pshard)) {
210986094b95STejun Heo 		scx_error(sch, "scx_bpf_sub_caps() called before caps storage is initialized");
211086094b95STejun Heo 		return -ENODEV;
211186094b95STejun Heo 	}
211286094b95STejun Heo 
211386094b95STejun Heo 	ret = scx_cmask_ref_init(sch, out__ign, &ref);
211486094b95STejun Heo 	if (ret) {
211586094b95STejun Heo 		scx_error(sch, "invalid out (%d)", ret);
211686094b95STejun Heo 		return ret;
211786094b95STejun Heo 	}
211886094b95STejun Heo 
211986094b95STejun Heo 	for (si = ref.shard_first; si < ref.shard_end; si++) {
21203a773220STejun Heo 		const struct scx_cid_shard *shard =
21213a773220STejun Heo 			&rcu_dereference_all(scx_cid_shard_ranges)[si];
212286094b95STejun Heo 		SCX_CMASK_DEFINE_SHARD(local_out, shard->base_cid, shard->nr_cids);
212386094b95STejun Heo 		u32 cap_bit;
212486094b95STejun Heo 
212586094b95STejun Heo 		scx_for_each_cap_bit(cap_bit, caps)
212686094b95STejun Heo 			scx_cmask_or(local_out, &pshard[si]->caps[cap_bit].cmask);
212786094b95STejun Heo 		scx_cmask_ref_copy(&ref, local_out);
212886094b95STejun Heo 	}
212986094b95STejun Heo 	return 0;
213086094b95STejun Heo }
213186094b95STejun Heo 
2132b0a2ca6aSTejun Heo /**
2133b0a2ca6aSTejun Heo  * scx_bpf_sub_kill_bstr - Kill a direct child sub-scheduler
2134b0a2ca6aSTejun Heo  * @cgroup_id: cgroup id of the direct child to kill
2135b0a2ca6aSTejun Heo  * @fmt: reason message format string
2136b0a2ca6aSTejun Heo  * @data: format string parameters packaged using ___bpf_fill() macro
2137b0a2ca6aSTejun Heo  * @data__sz: @data len, must end in '__sz' for the verifier
2138b0a2ca6aSTejun Heo  * @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
2139b0a2ca6aSTejun Heo  *
2140b0a2ca6aSTejun Heo  * Evict a direct child sub-scheduler, disabling it with the supplied reason.
2141b0a2ca6aSTejun Heo  * The child and its subtree are torn down asynchronously through the usual
2142b0a2ca6aSTejun Heo  * disable path.
2143b0a2ca6aSTejun Heo  *
2144b0a2ca6aSTejun Heo  * Unlike scx_bpf_exit(), no exit code is taken: the child is a separate
2145b0a2ca6aSTejun Heo  * scheduler with its own exit-code semantics, so a code chosen by the parent
2146b0a2ca6aSTejun Heo  * would have no defined meaning. The reason string carries the intent.
2147b0a2ca6aSTejun Heo  *
2148b0a2ca6aSTejun Heo  * Return 0 on success or -ENODEV if @cgroup_id names no sub-scheduler, which
2149b0a2ca6aSTejun Heo  * can race with the child detaching on its own and so is not a scheduler error.
2150b0a2ca6aSTejun Heo  * Naming a sched that exists but is not a direct child aborts the parent.
2151b0a2ca6aSTejun Heo  */
2152b0a2ca6aSTejun Heo __printf(2, 0)
2153b0a2ca6aSTejun Heo __bpf_kfunc s32 scx_bpf_sub_kill_bstr(u64 cgroup_id, char *fmt,
2154b0a2ca6aSTejun Heo 				      unsigned long long *data, u32 data__sz,
2155b0a2ca6aSTejun Heo 				      const struct bpf_prog_aux *aux)
2156b0a2ca6aSTejun Heo {
2157b0a2ca6aSTejun Heo 	struct scx_sched *parent, *child;
2158b0a2ca6aSTejun Heo 	s32 ret;
2159b0a2ca6aSTejun Heo 
2160b0a2ca6aSTejun Heo 	guard(rcu)();
2161b0a2ca6aSTejun Heo 
2162b0a2ca6aSTejun Heo 	parent = scx_prog_sched(aux);
2163b0a2ca6aSTejun Heo 	if (unlikely(!parent))
2164b0a2ca6aSTejun Heo 		return -ENODEV;
2165b0a2ca6aSTejun Heo 
2166b0a2ca6aSTejun Heo 	if (!scx_is_cid_type()) {
2167b0a2ca6aSTejun Heo 		scx_error(parent, "sub-cap kfuncs require a cid-form scheduler");
2168b0a2ca6aSTejun Heo 		return -EOPNOTSUPP;
2169b0a2ca6aSTejun Heo 	}
2170b0a2ca6aSTejun Heo 
2171b0a2ca6aSTejun Heo 	child = scx_find_sub_sched(cgroup_id);
2172b0a2ca6aSTejun Heo 	if (unlikely(!child))
2173b0a2ca6aSTejun Heo 		return -ENODEV;
2174b0a2ca6aSTejun Heo 
2175b0a2ca6aSTejun Heo 	if (unlikely(scx_parent(child) != parent)) {
2176b0a2ca6aSTejun Heo 		scx_error(parent, "%s: sub-%llu is not a direct child",
2177b0a2ca6aSTejun Heo 			  parent->cgrp_path, cgroup_id);
2178b0a2ca6aSTejun Heo 		return -EINVAL;
2179b0a2ca6aSTejun Heo 	}
2180b0a2ca6aSTejun Heo 
2181b0a2ca6aSTejun Heo 	guard(raw_spinlock_irqsave)(&scx_exit_bstr_buf_lock);
2182b0a2ca6aSTejun Heo 	ret = scx_bstr_format(parent, &scx_exit_bstr_buf, fmt, data, data__sz);
2183b0a2ca6aSTejun Heo 	if (ret < 0)
2184b0a2ca6aSTejun Heo 		return ret;
2185b0a2ca6aSTejun Heo 	scx_exit(child, SCX_EXIT_PARENT_KILL, 0, "%s", scx_exit_bstr_buf.line);
2186b0a2ca6aSTejun Heo 	return 0;
2187b0a2ca6aSTejun Heo }
2188b0a2ca6aSTejun Heo 
2189daf8e166STejun Heo __bpf_kfunc_end_defs();
2190daf8e166STejun Heo 
2191daf8e166STejun Heo #endif	/* CONFIG_EXT_SUB_SCHED */
2192