xref: /linux/kernel/sched/ext/sub.c (revision daf8e166ba59ddf1dacb080228cb42e5d1de30ae)
1*daf8e166STejun Heo // SPDX-License-Identifier: GPL-2.0
2*daf8e166STejun Heo /*
3*daf8e166STejun Heo  * BPF extensible scheduler class: Documentation/scheduler/sched-ext.rst
4*daf8e166STejun Heo  *
5*daf8e166STejun Heo  * Sub-scheduler hierarchy support.
6*daf8e166STejun Heo  *
7*daf8e166STejun Heo  * A sub-scheduler is an scx_sched attached to a cgroup subtree under another
8*daf8e166STejun Heo  * scx_sched. This file holds the sub-scheduler implementation: the scheduler
9*daf8e166STejun Heo  * tree walk, capability delegation, per-shard cap state and its sync, and the
10*daf8e166STejun Heo  * sub-scheduler enable/disable paths. The core dispatch/enqueue machinery it
11*daf8e166STejun Heo  * builds on lives in ext.c.
12*daf8e166STejun Heo  *
13*daf8e166STejun Heo  * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
14*daf8e166STejun Heo  * Copyright (c) 2026 Tejun Heo <tj@kernel.org>
15*daf8e166STejun Heo  */
16*daf8e166STejun Heo #include <linux/rhashtable.h>
17*daf8e166STejun Heo #include "internal.h"
18*daf8e166STejun Heo #include "cid.h"
19*daf8e166STejun Heo #include "arena.h"
20*daf8e166STejun Heo #include "sub.h"
21*daf8e166STejun Heo 
22*daf8e166STejun Heo #ifdef CONFIG_EXT_SUB_SCHED
23*daf8e166STejun Heo 
24*daf8e166STejun Heo /**
25*daf8e166STejun Heo  * scx_next_descendant_pre - find the next descendant for pre-order walk
26*daf8e166STejun Heo  * @pos: the current position (%NULL to initiate traversal)
27*daf8e166STejun Heo  * @root: sched whose descendants to walk
28*daf8e166STejun Heo  *
29*daf8e166STejun Heo  * To be used by scx_for_each_descendant_pre(). Find the next descendant to
30*daf8e166STejun Heo  * visit for pre-order traversal of @root's descendants. @root is included in
31*daf8e166STejun Heo  * the iteration and the first node to be visited.
32*daf8e166STejun Heo  */
33*daf8e166STejun Heo struct scx_sched *scx_next_descendant_pre(struct scx_sched *pos, struct scx_sched *root)
34*daf8e166STejun Heo {
35*daf8e166STejun Heo 	struct scx_sched *next;
36*daf8e166STejun Heo 
37*daf8e166STejun Heo 	lockdep_assert(lockdep_is_held(&scx_enable_mutex) ||
38*daf8e166STejun Heo 		       lockdep_is_held(&scx_sched_lock));
39*daf8e166STejun Heo 
40*daf8e166STejun Heo 	/* if first iteration, visit @root */
41*daf8e166STejun Heo 	if (!pos)
42*daf8e166STejun Heo 		return root;
43*daf8e166STejun Heo 
44*daf8e166STejun Heo 	/* visit the first child if exists */
45*daf8e166STejun Heo 	next = list_first_entry_or_null(&pos->children, struct scx_sched, sibling);
46*daf8e166STejun Heo 	if (next)
47*daf8e166STejun Heo 		return next;
48*daf8e166STejun Heo 
49*daf8e166STejun Heo 	/* no child, visit my or the closest ancestor's next sibling */
50*daf8e166STejun Heo 	while (pos != root) {
51*daf8e166STejun Heo 		if (!list_is_last(&pos->sibling, &scx_parent(pos)->children))
52*daf8e166STejun Heo 			return list_next_entry(pos, sibling);
53*daf8e166STejun Heo 		pos = scx_parent(pos);
54*daf8e166STejun Heo 	}
55*daf8e166STejun Heo 
56*daf8e166STejun Heo 	return NULL;
57*daf8e166STejun Heo }
58*daf8e166STejun Heo 
59*daf8e166STejun Heo static struct scx_sched *scx_find_sub_sched(u64 cgroup_id)
60*daf8e166STejun Heo {
61*daf8e166STejun Heo 	return rhashtable_lookup(&scx_sched_hash, &cgroup_id,
62*daf8e166STejun Heo 				 scx_sched_hash_params);
63*daf8e166STejun Heo }
64*daf8e166STejun Heo 
65*daf8e166STejun Heo void scx_set_task_sched(struct task_struct *p, struct scx_sched *sch)
66*daf8e166STejun Heo {
67*daf8e166STejun Heo 	rcu_assign_pointer(p->scx.sched, sch);
68*daf8e166STejun Heo }
69*daf8e166STejun Heo 
70*daf8e166STejun Heo struct cgroup *sch_cgroup(struct scx_sched *sch)
71*daf8e166STejun Heo {
72*daf8e166STejun Heo 	return sch->cgrp;
73*daf8e166STejun Heo }
74*daf8e166STejun Heo 
75*daf8e166STejun Heo /* for each descendant of @cgrp including self, set ->scx_sched to @sch */
76*daf8e166STejun Heo void set_cgroup_sched(struct cgroup *cgrp, struct scx_sched *sch)
77*daf8e166STejun Heo {
78*daf8e166STejun Heo 	struct cgroup *pos;
79*daf8e166STejun Heo 	struct cgroup_subsys_state *css;
80*daf8e166STejun Heo 
81*daf8e166STejun Heo 	cgroup_for_each_live_descendant_pre(pos, css, cgrp)
82*daf8e166STejun Heo 		rcu_assign_pointer(pos->scx_sched, sch);
83*daf8e166STejun Heo }
84*daf8e166STejun Heo 
85*daf8e166STejun Heo static DECLARE_WAIT_QUEUE_HEAD(scx_unlink_waitq);
86*daf8e166STejun Heo 
87*daf8e166STejun Heo void drain_descendants(struct scx_sched *sch)
88*daf8e166STejun Heo {
89*daf8e166STejun Heo 	/*
90*daf8e166STejun Heo 	 * Child scheds that finished the critical part of disabling will take
91*daf8e166STejun Heo 	 * themselves off @sch->children. Wait for it to drain. As propagation
92*daf8e166STejun Heo 	 * is recursive, empty @sch->children means that all proper descendant
93*daf8e166STejun Heo 	 * scheds reached unlinking stage.
94*daf8e166STejun Heo 	 */
95*daf8e166STejun Heo 	wait_event(scx_unlink_waitq, list_empty(&sch->children));
96*daf8e166STejun Heo }
97*daf8e166STejun Heo 
98*daf8e166STejun Heo static void scx_fail_parent(struct scx_sched *sch,
99*daf8e166STejun Heo 			    struct task_struct *failed, s32 fail_code)
100*daf8e166STejun Heo {
101*daf8e166STejun Heo 	struct scx_sched *parent = scx_parent(sch);
102*daf8e166STejun Heo 	struct scx_task_iter sti;
103*daf8e166STejun Heo 	struct task_struct *p;
104*daf8e166STejun Heo 
105*daf8e166STejun Heo 	scx_error(parent, "ops.init_task() failed (%d) for %s[%d] while disabling a sub-scheduler",
106*daf8e166STejun Heo 		  fail_code, failed->comm, failed->pid);
107*daf8e166STejun Heo 
108*daf8e166STejun Heo 	/*
109*daf8e166STejun Heo 	 * Once $parent is bypassed, it's safe to put SCX_TASK_NONE tasks into
110*daf8e166STejun Heo 	 * it. This may cause downstream failures on the BPF side but $parent is
111*daf8e166STejun Heo 	 * dying anyway.
112*daf8e166STejun Heo 	 */
113*daf8e166STejun Heo 	scx_bypass(parent, true);
114*daf8e166STejun Heo 
115*daf8e166STejun Heo 	scx_task_iter_start(&sti, sch->cgrp);
116*daf8e166STejun Heo 	while ((p = scx_task_iter_next_locked(&sti))) {
117*daf8e166STejun Heo 		if (scx_task_on_sched(parent, p))
118*daf8e166STejun Heo 			continue;
119*daf8e166STejun Heo 
120*daf8e166STejun Heo 		scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE) {
121*daf8e166STejun Heo 			scx_disable_and_exit_task(sch, p);
122*daf8e166STejun Heo 			scx_set_task_sched(p, parent);
123*daf8e166STejun Heo 		}
124*daf8e166STejun Heo 	}
125*daf8e166STejun Heo 	scx_task_iter_stop(&sti);
126*daf8e166STejun Heo }
127*daf8e166STejun Heo 
128*daf8e166STejun Heo void scx_sub_disable(struct scx_sched *sch)
129*daf8e166STejun Heo {
130*daf8e166STejun Heo 	struct scx_sched *parent = scx_parent(sch);
131*daf8e166STejun Heo 	struct scx_task_iter sti;
132*daf8e166STejun Heo 	struct task_struct *p;
133*daf8e166STejun Heo 	int ret;
134*daf8e166STejun Heo 
135*daf8e166STejun Heo 	/*
136*daf8e166STejun Heo 	 * Guarantee forward progress and wait for descendants to be disabled.
137*daf8e166STejun Heo 	 * To limit disruptions, $parent is not bypassed. Tasks are fully
138*daf8e166STejun Heo 	 * prepped and then inserted back into $parent.
139*daf8e166STejun Heo 	 */
140*daf8e166STejun Heo 	scx_bypass(sch, true);
141*daf8e166STejun Heo 	drain_descendants(sch);
142*daf8e166STejun Heo 
143*daf8e166STejun Heo 	/*
144*daf8e166STejun Heo 	 * Here, every runnable task is guaranteed to make forward progress and
145*daf8e166STejun Heo 	 * we can safely use blocking synchronization constructs. Actually
146*daf8e166STejun Heo 	 * disable ops.
147*daf8e166STejun Heo 	 */
148*daf8e166STejun Heo 	mutex_lock(&scx_enable_mutex);
149*daf8e166STejun Heo 	percpu_down_write(&scx_fork_rwsem);
150*daf8e166STejun Heo 	scx_cgroup_lock();
151*daf8e166STejun Heo 
152*daf8e166STejun Heo 	set_cgroup_sched(sch_cgroup(sch), parent);
153*daf8e166STejun Heo 
154*daf8e166STejun Heo 	scx_task_iter_start(&sti, sch->cgrp);
155*daf8e166STejun Heo 	while ((p = scx_task_iter_next_locked(&sti))) {
156*daf8e166STejun Heo 		struct rq *rq;
157*daf8e166STejun Heo 		struct rq_flags rf;
158*daf8e166STejun Heo 
159*daf8e166STejun Heo 		/* filter out duplicate visits */
160*daf8e166STejun Heo 		if (scx_task_on_sched(parent, p))
161*daf8e166STejun Heo 			continue;
162*daf8e166STejun Heo 
163*daf8e166STejun Heo 		/*
164*daf8e166STejun Heo 		 * By the time control reaches here, all descendant schedulers
165*daf8e166STejun Heo 		 * should already have been disabled.
166*daf8e166STejun Heo 		 */
167*daf8e166STejun Heo 		WARN_ON_ONCE(!scx_task_on_sched(sch, p));
168*daf8e166STejun Heo 
169*daf8e166STejun Heo 		/*
170*daf8e166STejun Heo 		 * @p is pinned by the iter: css_task_iter_next() takes a
171*daf8e166STejun Heo 		 * reference and holds it until the next iter_next() call, so
172*daf8e166STejun Heo 		 * @p->usage is guaranteed > 0.
173*daf8e166STejun Heo 		 */
174*daf8e166STejun Heo 		get_task_struct(p);
175*daf8e166STejun Heo 
176*daf8e166STejun Heo 		scx_task_iter_unlock(&sti);
177*daf8e166STejun Heo 
178*daf8e166STejun Heo 		/*
179*daf8e166STejun Heo 		 * $p is READY or ENABLED on @sch. Initialize for $parent,
180*daf8e166STejun Heo 		 * disable and exit from @sch, and then switch over to $parent.
181*daf8e166STejun Heo 		 *
182*daf8e166STejun Heo 		 * If a task fails to initialize for $parent, the only available
183*daf8e166STejun Heo 		 * action is disabling $parent too. While this allows disabling
184*daf8e166STejun Heo 		 * of a child sched to cause the parent scheduler to fail, the
185*daf8e166STejun Heo 		 * failure can only originate from ops.init_task() of the
186*daf8e166STejun Heo 		 * parent. A child can't directly affect the parent through its
187*daf8e166STejun Heo 		 * own failures.
188*daf8e166STejun Heo 		 */
189*daf8e166STejun Heo 		ret = __scx_init_task(parent, p, false);
190*daf8e166STejun Heo 		if (ret) {
191*daf8e166STejun Heo 			scx_fail_parent(sch, p, ret);
192*daf8e166STejun Heo 			put_task_struct(p);
193*daf8e166STejun Heo 			break;
194*daf8e166STejun Heo 		}
195*daf8e166STejun Heo 
196*daf8e166STejun Heo 		rq = task_rq_lock(p, &rf);
197*daf8e166STejun Heo 
198*daf8e166STejun Heo 		if (scx_get_task_state(p) == SCX_TASK_DEAD) {
199*daf8e166STejun Heo 			/*
200*daf8e166STejun Heo 			 * sched_ext_dead() raced us between __scx_init_task()
201*daf8e166STejun Heo 			 * and this rq lock and ran exit_task() on @sch (the
202*daf8e166STejun Heo 			 * sched @p was on at that point), not on $parent.
203*daf8e166STejun Heo 			 * $parent's just-completed init is owed an exit_task()
204*daf8e166STejun Heo 			 * and we issue it here.
205*daf8e166STejun Heo 			 */
206*daf8e166STejun Heo 			scx_sub_init_cancel_task(parent, p);
207*daf8e166STejun Heo 			task_rq_unlock(rq, p, &rf);
208*daf8e166STejun Heo 			put_task_struct(p);
209*daf8e166STejun Heo 			continue;
210*daf8e166STejun Heo 		}
211*daf8e166STejun Heo 
212*daf8e166STejun Heo 		scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE) {
213*daf8e166STejun Heo 			/*
214*daf8e166STejun Heo 			 * $p is initialized for $parent and still attached to
215*daf8e166STejun Heo 			 * @sch. Disable and exit for @sch, switch over to
216*daf8e166STejun Heo 			 * $parent, override the state to READY to account for
217*daf8e166STejun Heo 			 * $p having already been initialized, and then enable.
218*daf8e166STejun Heo 			 */
219*daf8e166STejun Heo 			scx_disable_and_exit_task(sch, p);
220*daf8e166STejun Heo 			scx_set_task_state(p, SCX_TASK_INIT_BEGIN);
221*daf8e166STejun Heo 			scx_set_task_state(p, SCX_TASK_INIT);
222*daf8e166STejun Heo 			scx_set_task_sched(p, parent);
223*daf8e166STejun Heo 			scx_set_task_state(p, SCX_TASK_READY);
224*daf8e166STejun Heo 			scx_enable_task(parent, p);
225*daf8e166STejun Heo 		}
226*daf8e166STejun Heo 
227*daf8e166STejun Heo 		task_rq_unlock(rq, p, &rf);
228*daf8e166STejun Heo 		put_task_struct(p);
229*daf8e166STejun Heo 	}
230*daf8e166STejun Heo 	scx_task_iter_stop(&sti);
231*daf8e166STejun Heo 
232*daf8e166STejun Heo 	scx_disable_dump(sch);
233*daf8e166STejun Heo 
234*daf8e166STejun Heo 	scx_cgroup_unlock();
235*daf8e166STejun Heo 	percpu_up_write(&scx_fork_rwsem);
236*daf8e166STejun Heo 
237*daf8e166STejun Heo 	/*
238*daf8e166STejun Heo 	 * All tasks are moved off of @sch but there may still be on-going
239*daf8e166STejun Heo 	 * operations (e.g. ops.select_cpu()). Drain them by flushing RCU. Use
240*daf8e166STejun Heo 	 * the expedited version as ancestors may be waiting in bypass mode.
241*daf8e166STejun Heo 	 * Also, tell the parent that there is no need to keep running bypass
242*daf8e166STejun Heo 	 * DSQs for us.
243*daf8e166STejun Heo 	 */
244*daf8e166STejun Heo 	synchronize_rcu_expedited();
245*daf8e166STejun Heo 	scx_disable_bypass_dsp(sch);
246*daf8e166STejun Heo 
247*daf8e166STejun Heo 	scx_unlink_sched(sch);
248*daf8e166STejun Heo 
249*daf8e166STejun Heo 	mutex_unlock(&scx_enable_mutex);
250*daf8e166STejun Heo 
251*daf8e166STejun Heo 	/*
252*daf8e166STejun Heo 	 * @sch is now unlinked from the parent's children list. Notify and call
253*daf8e166STejun Heo 	 * ops.sub_detach/exit(). Note that ops.sub_detach/exit() must be called
254*daf8e166STejun Heo 	 * after unlinking and releasing all locks. See scx_claim_exit().
255*daf8e166STejun Heo 	 */
256*daf8e166STejun Heo 	wake_up_all(&scx_unlink_waitq);
257*daf8e166STejun Heo 
258*daf8e166STejun Heo 	if (parent->ops.sub_detach && sch->sub_attached) {
259*daf8e166STejun Heo 		struct scx_sub_detach_args sub_detach_args = {
260*daf8e166STejun Heo 			.ops = &sch->ops,
261*daf8e166STejun Heo 			.cgroup_path = sch->cgrp_path,
262*daf8e166STejun Heo 		};
263*daf8e166STejun Heo 		SCX_CALL_OP(parent, sub_detach, NULL,
264*daf8e166STejun Heo 			    &sub_detach_args);
265*daf8e166STejun Heo 	}
266*daf8e166STejun Heo 
267*daf8e166STejun Heo 	scx_log_sched_disable(sch);
268*daf8e166STejun Heo 
269*daf8e166STejun Heo 	if (sch->ops.exit)
270*daf8e166STejun Heo 		SCX_CALL_OP(sch, exit, NULL, sch->exit_info);
271*daf8e166STejun Heo 	if (sch->sub_kset)
272*daf8e166STejun Heo 		kobject_del(&sch->sub_kset->kobj);
273*daf8e166STejun Heo 	kobject_del(&sch->kobj);
274*daf8e166STejun Heo }
275*daf8e166STejun Heo 
276*daf8e166STejun Heo /* verify that a scheduler can be attached to @cgrp and return the parent */
277*daf8e166STejun Heo static struct scx_sched *find_parent_sched(struct cgroup *cgrp)
278*daf8e166STejun Heo {
279*daf8e166STejun Heo 	struct scx_sched *parent = cgrp->scx_sched;
280*daf8e166STejun Heo 	struct scx_sched *pos;
281*daf8e166STejun Heo 
282*daf8e166STejun Heo 	lockdep_assert_held(&scx_sched_lock);
283*daf8e166STejun Heo 
284*daf8e166STejun Heo 	/* can't attach twice to the same cgroup */
285*daf8e166STejun Heo 	if (parent->cgrp == cgrp)
286*daf8e166STejun Heo 		return ERR_PTR(-EBUSY);
287*daf8e166STejun Heo 
288*daf8e166STejun Heo 	/* does $parent allow sub-scheds? */
289*daf8e166STejun Heo 	if (!parent->ops.sub_attach)
290*daf8e166STejun Heo 		return ERR_PTR(-EOPNOTSUPP);
291*daf8e166STejun Heo 
292*daf8e166STejun Heo 	/* can't insert between $parent and its exiting children */
293*daf8e166STejun Heo 	list_for_each_entry(pos, &parent->children, sibling)
294*daf8e166STejun Heo 		if (cgroup_is_descendant(pos->cgrp, cgrp))
295*daf8e166STejun Heo 			return ERR_PTR(-EBUSY);
296*daf8e166STejun Heo 
297*daf8e166STejun Heo 	return parent;
298*daf8e166STejun Heo }
299*daf8e166STejun Heo 
300*daf8e166STejun Heo static bool assert_task_ready_or_enabled(struct task_struct *p)
301*daf8e166STejun Heo {
302*daf8e166STejun Heo 	u32 state = scx_get_task_state(p);
303*daf8e166STejun Heo 
304*daf8e166STejun Heo 	switch (state) {
305*daf8e166STejun Heo 	case SCX_TASK_READY:
306*daf8e166STejun Heo 	case SCX_TASK_ENABLED:
307*daf8e166STejun Heo 		return true;
308*daf8e166STejun Heo 	default:
309*daf8e166STejun Heo 		WARN_ONCE(true, "sched_ext: Invalid task state %d for %s[%d] during enabling sub sched",
310*daf8e166STejun Heo 			  state, p->comm, p->pid);
311*daf8e166STejun Heo 		return false;
312*daf8e166STejun Heo 	}
313*daf8e166STejun Heo }
314*daf8e166STejun Heo 
315*daf8e166STejun Heo void scx_sub_enable_workfn(struct kthread_work *work)
316*daf8e166STejun Heo {
317*daf8e166STejun Heo 	struct scx_enable_cmd *cmd = container_of(work, struct scx_enable_cmd, work);
318*daf8e166STejun Heo 	struct sched_ext_ops *ops = cmd->ops;
319*daf8e166STejun Heo 	struct cgroup *cgrp;
320*daf8e166STejun Heo 	struct scx_sched *parent, *sch;
321*daf8e166STejun Heo 	struct scx_task_iter sti;
322*daf8e166STejun Heo 	struct task_struct *p;
323*daf8e166STejun Heo 	s32 i, ret;
324*daf8e166STejun Heo 
325*daf8e166STejun Heo 	mutex_lock(&scx_enable_mutex);
326*daf8e166STejun Heo 
327*daf8e166STejun Heo 	if (!scx_enabled()) {
328*daf8e166STejun Heo 		ret = -ENODEV;
329*daf8e166STejun Heo 		goto out_unlock;
330*daf8e166STejun Heo 	}
331*daf8e166STejun Heo 
332*daf8e166STejun Heo 	/* See scx_root_enable_workfn() for the @ops->priv check. */
333*daf8e166STejun Heo 	if (rcu_access_pointer(ops->priv)) {
334*daf8e166STejun Heo 		ret = -EBUSY;
335*daf8e166STejun Heo 		goto out_unlock;
336*daf8e166STejun Heo 	}
337*daf8e166STejun Heo 
338*daf8e166STejun Heo 	cgrp = cgroup_get_from_id(ops->sub_cgroup_id);
339*daf8e166STejun Heo 	if (IS_ERR(cgrp)) {
340*daf8e166STejun Heo 		ret = PTR_ERR(cgrp);
341*daf8e166STejun Heo 		goto out_unlock;
342*daf8e166STejun Heo 	}
343*daf8e166STejun Heo 
344*daf8e166STejun Heo 	raw_spin_lock_irq(&scx_sched_lock);
345*daf8e166STejun Heo 	parent = find_parent_sched(cgrp);
346*daf8e166STejun Heo 	if (IS_ERR(parent)) {
347*daf8e166STejun Heo 		raw_spin_unlock_irq(&scx_sched_lock);
348*daf8e166STejun Heo 		ret = PTR_ERR(parent);
349*daf8e166STejun Heo 		goto out_put_cgrp;
350*daf8e166STejun Heo 	}
351*daf8e166STejun Heo 	kobject_get(&parent->kobj);
352*daf8e166STejun Heo 	raw_spin_unlock_irq(&scx_sched_lock);
353*daf8e166STejun Heo 
354*daf8e166STejun Heo 	/* scx_alloc_and_add_sched() consumes @cgrp whether it succeeds or not */
355*daf8e166STejun Heo 	sch = scx_alloc_and_add_sched(cmd, cgrp, parent);
356*daf8e166STejun Heo 	kobject_put(&parent->kobj);
357*daf8e166STejun Heo 	if (IS_ERR(sch)) {
358*daf8e166STejun Heo 		ret = PTR_ERR(sch);
359*daf8e166STejun Heo 		goto out_unlock;
360*daf8e166STejun Heo 	}
361*daf8e166STejun Heo 
362*daf8e166STejun Heo 	ret = scx_link_sched(sch);
363*daf8e166STejun Heo 	if (ret)
364*daf8e166STejun Heo 		goto err_disable;
365*daf8e166STejun Heo 
366*daf8e166STejun Heo 	if (sch->level >= SCX_SUB_MAX_DEPTH) {
367*daf8e166STejun Heo 		scx_error(sch, "max nesting depth %d violated",
368*daf8e166STejun Heo 			  SCX_SUB_MAX_DEPTH);
369*daf8e166STejun Heo 		goto err_disable;
370*daf8e166STejun Heo 	}
371*daf8e166STejun Heo 
372*daf8e166STejun Heo 	if (sch->ops.init) {
373*daf8e166STejun Heo 		ret = SCX_CALL_OP_RET(sch, init, NULL);
374*daf8e166STejun Heo 		if (ret) {
375*daf8e166STejun Heo 			ret = scx_ops_sanitize_err(sch, "init", ret);
376*daf8e166STejun Heo 			scx_error(sch, "ops.init() failed (%d)", ret);
377*daf8e166STejun Heo 			goto err_disable;
378*daf8e166STejun Heo 		}
379*daf8e166STejun Heo 		sch->exit_info->flags |= SCX_EFLAG_INITIALIZED;
380*daf8e166STejun Heo 	}
381*daf8e166STejun Heo 
382*daf8e166STejun Heo 	ret = scx_arena_pool_init(sch);
383*daf8e166STejun Heo 	if (ret)
384*daf8e166STejun Heo 		goto err_disable;
385*daf8e166STejun Heo 
386*daf8e166STejun Heo 	ret = scx_set_cmask_scratch_alloc(sch);
387*daf8e166STejun Heo 	if (ret)
388*daf8e166STejun Heo 		goto err_disable;
389*daf8e166STejun Heo 
390*daf8e166STejun Heo 	if (scx_validate_ops(sch, ops))
391*daf8e166STejun Heo 		goto err_disable;
392*daf8e166STejun Heo 
393*daf8e166STejun Heo 	struct scx_sub_attach_args sub_attach_args = {
394*daf8e166STejun Heo 		.ops = &sch->ops,
395*daf8e166STejun Heo 		.cgroup_path = sch->cgrp_path,
396*daf8e166STejun Heo 	};
397*daf8e166STejun Heo 
398*daf8e166STejun Heo 	ret = SCX_CALL_OP_RET(parent, sub_attach, NULL,
399*daf8e166STejun Heo 			      &sub_attach_args);
400*daf8e166STejun Heo 	if (ret) {
401*daf8e166STejun Heo 		ret = scx_ops_sanitize_err(sch, "sub_attach", ret);
402*daf8e166STejun Heo 		scx_error(sch, "parent rejected (%d)", ret);
403*daf8e166STejun Heo 		goto err_disable;
404*daf8e166STejun Heo 	}
405*daf8e166STejun Heo 	sch->sub_attached = true;
406*daf8e166STejun Heo 
407*daf8e166STejun Heo 	scx_bypass(sch, true);
408*daf8e166STejun Heo 
409*daf8e166STejun Heo 	for (i = SCX_OPI_BEGIN; i < SCX_OPI_END; i++)
410*daf8e166STejun Heo 		if (((void (**)(void))ops)[i])
411*daf8e166STejun Heo 			set_bit(i, sch->has_op);
412*daf8e166STejun Heo 
413*daf8e166STejun Heo 	percpu_down_write(&scx_fork_rwsem);
414*daf8e166STejun Heo 	scx_cgroup_lock();
415*daf8e166STejun Heo 
416*daf8e166STejun Heo 	/*
417*daf8e166STejun Heo 	 * Set cgroup->scx_sched's and check CSS_ONLINE. Either we see
418*daf8e166STejun Heo 	 * !CSS_ONLINE or scx_cgroup_lifetime_notify() sees and shoots us down.
419*daf8e166STejun Heo 	 */
420*daf8e166STejun Heo 	set_cgroup_sched(sch_cgroup(sch), sch);
421*daf8e166STejun Heo 	if (!(cgrp->self.flags & CSS_ONLINE)) {
422*daf8e166STejun Heo 		scx_error(sch, "cgroup is not online");
423*daf8e166STejun Heo 		goto err_unlock_and_disable;
424*daf8e166STejun Heo 	}
425*daf8e166STejun Heo 
426*daf8e166STejun Heo 	/*
427*daf8e166STejun Heo 	 * Initialize tasks for the new child $sch without exiting them for
428*daf8e166STejun Heo 	 * $parent so that the tasks can always be reverted back to $parent
429*daf8e166STejun Heo 	 * sched on child init failure.
430*daf8e166STejun Heo 	 */
431*daf8e166STejun Heo 	WARN_ON_ONCE(scx_enabling_sub_sched);
432*daf8e166STejun Heo 	scx_enabling_sub_sched = sch;
433*daf8e166STejun Heo 
434*daf8e166STejun Heo 	scx_task_iter_start(&sti, sch->cgrp);
435*daf8e166STejun Heo 	while ((p = scx_task_iter_next_locked(&sti))) {
436*daf8e166STejun Heo 		struct rq *rq;
437*daf8e166STejun Heo 		struct rq_flags rf;
438*daf8e166STejun Heo 
439*daf8e166STejun Heo 		/*
440*daf8e166STejun Heo 		 * Task iteration may visit the same task twice when racing
441*daf8e166STejun Heo 		 * against exiting. Use %SCX_TASK_SUB_INIT to mark tasks which
442*daf8e166STejun Heo 		 * finished __scx_init_task() and skip if set.
443*daf8e166STejun Heo 		 *
444*daf8e166STejun Heo 		 * A task may exit and get freed between __scx_init_task()
445*daf8e166STejun Heo 		 * completion and scx_enable_task(). In such cases,
446*daf8e166STejun Heo 		 * scx_disable_and_exit_task() must exit the task for both the
447*daf8e166STejun Heo 		 * parent and child scheds.
448*daf8e166STejun Heo 		 */
449*daf8e166STejun Heo 		if (p->scx.flags & SCX_TASK_SUB_INIT)
450*daf8e166STejun Heo 			continue;
451*daf8e166STejun Heo 
452*daf8e166STejun Heo 		/* @p is pinned by the iter; see scx_sub_disable() */
453*daf8e166STejun Heo 		get_task_struct(p);
454*daf8e166STejun Heo 
455*daf8e166STejun Heo 		if (!assert_task_ready_or_enabled(p)) {
456*daf8e166STejun Heo 			ret = -EINVAL;
457*daf8e166STejun Heo 			goto abort;
458*daf8e166STejun Heo 		}
459*daf8e166STejun Heo 
460*daf8e166STejun Heo 		scx_task_iter_unlock(&sti);
461*daf8e166STejun Heo 
462*daf8e166STejun Heo 		/*
463*daf8e166STejun Heo 		 * As $p is still on $parent, it can't be transitioned to INIT.
464*daf8e166STejun Heo 		 * Let's worry about task state later. Use __scx_init_task().
465*daf8e166STejun Heo 		 */
466*daf8e166STejun Heo 		ret = __scx_init_task(sch, p, false);
467*daf8e166STejun Heo 		if (ret)
468*daf8e166STejun Heo 			goto abort;
469*daf8e166STejun Heo 
470*daf8e166STejun Heo 		rq = task_rq_lock(p, &rf);
471*daf8e166STejun Heo 
472*daf8e166STejun Heo 		if (scx_get_task_state(p) == SCX_TASK_DEAD) {
473*daf8e166STejun Heo 			/*
474*daf8e166STejun Heo 			 * sched_ext_dead() raced us between __scx_init_task()
475*daf8e166STejun Heo 			 * and this rq lock and ran exit_task() on $parent (the
476*daf8e166STejun Heo 			 * sched @p was on at that point), not on @sch. @sch's
477*daf8e166STejun Heo 			 * just-completed init is owed an exit_task() and we
478*daf8e166STejun Heo 			 * issue it here.
479*daf8e166STejun Heo 			 */
480*daf8e166STejun Heo 			scx_sub_init_cancel_task(sch, p);
481*daf8e166STejun Heo 			task_rq_unlock(rq, p, &rf);
482*daf8e166STejun Heo 			put_task_struct(p);
483*daf8e166STejun Heo 			continue;
484*daf8e166STejun Heo 		}
485*daf8e166STejun Heo 
486*daf8e166STejun Heo 		p->scx.flags |= SCX_TASK_SUB_INIT;
487*daf8e166STejun Heo 		task_rq_unlock(rq, p, &rf);
488*daf8e166STejun Heo 
489*daf8e166STejun Heo 		put_task_struct(p);
490*daf8e166STejun Heo 	}
491*daf8e166STejun Heo 	scx_task_iter_stop(&sti);
492*daf8e166STejun Heo 
493*daf8e166STejun Heo 	/*
494*daf8e166STejun Heo 	 * All tasks are prepped. Disable/exit tasks for $parent and enable for
495*daf8e166STejun Heo 	 * the new @sch.
496*daf8e166STejun Heo 	 */
497*daf8e166STejun Heo 	scx_task_iter_start(&sti, sch->cgrp);
498*daf8e166STejun Heo 	while ((p = scx_task_iter_next_locked(&sti))) {
499*daf8e166STejun Heo 		/*
500*daf8e166STejun Heo 		 * Use clearing of %SCX_TASK_SUB_INIT to detect and skip
501*daf8e166STejun Heo 		 * duplicate iterations.
502*daf8e166STejun Heo 		 */
503*daf8e166STejun Heo 		if (!(p->scx.flags & SCX_TASK_SUB_INIT))
504*daf8e166STejun Heo 			continue;
505*daf8e166STejun Heo 
506*daf8e166STejun Heo 		scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE) {
507*daf8e166STejun Heo 			/*
508*daf8e166STejun Heo 			 * $p must be either READY or ENABLED. If ENABLED,
509*daf8e166STejun Heo 			 * __scx_disabled_and_exit_task() first disables and
510*daf8e166STejun Heo 			 * makes it READY. However, after exiting $p, it will
511*daf8e166STejun Heo 			 * leave $p as READY.
512*daf8e166STejun Heo 			 */
513*daf8e166STejun Heo 			assert_task_ready_or_enabled(p);
514*daf8e166STejun Heo 			__scx_disable_and_exit_task(parent, p);
515*daf8e166STejun Heo 
516*daf8e166STejun Heo 			/*
517*daf8e166STejun Heo 			 * $p is now only initialized for @sch and READY, which
518*daf8e166STejun Heo 			 * is what we want. Assign it to @sch and enable.
519*daf8e166STejun Heo 			 */
520*daf8e166STejun Heo 			scx_set_task_sched(p, sch);
521*daf8e166STejun Heo 			scx_enable_task(sch, p);
522*daf8e166STejun Heo 
523*daf8e166STejun Heo 			p->scx.flags &= ~SCX_TASK_SUB_INIT;
524*daf8e166STejun Heo 		}
525*daf8e166STejun Heo 	}
526*daf8e166STejun Heo 	scx_task_iter_stop(&sti);
527*daf8e166STejun Heo 
528*daf8e166STejun Heo 	scx_enabling_sub_sched = NULL;
529*daf8e166STejun Heo 
530*daf8e166STejun Heo 	scx_cgroup_unlock();
531*daf8e166STejun Heo 	percpu_up_write(&scx_fork_rwsem);
532*daf8e166STejun Heo 
533*daf8e166STejun Heo 	scx_bypass(sch, false);
534*daf8e166STejun Heo 
535*daf8e166STejun Heo 	pr_info("sched_ext: BPF sub-scheduler \"%s\" enabled\n", sch->ops.name);
536*daf8e166STejun Heo 	kobject_uevent(&sch->kobj, KOBJ_ADD);
537*daf8e166STejun Heo 	ret = 0;
538*daf8e166STejun Heo 	goto out_unlock;
539*daf8e166STejun Heo 
540*daf8e166STejun Heo out_put_cgrp:
541*daf8e166STejun Heo 	cgroup_put(cgrp);
542*daf8e166STejun Heo out_unlock:
543*daf8e166STejun Heo 	mutex_unlock(&scx_enable_mutex);
544*daf8e166STejun Heo 	cmd->ret = ret;
545*daf8e166STejun Heo 	return;
546*daf8e166STejun Heo 
547*daf8e166STejun Heo abort:
548*daf8e166STejun Heo 	put_task_struct(p);
549*daf8e166STejun Heo 	scx_task_iter_stop(&sti);
550*daf8e166STejun Heo 
551*daf8e166STejun Heo 	/*
552*daf8e166STejun Heo 	 * Undo __scx_init_task() for tasks we marked. scx_enable_task() never
553*daf8e166STejun Heo 	 * ran for @sch on them, so calling scx_disable_task() here would invoke
554*daf8e166STejun Heo 	 * ops.disable() without a matching ops.enable(). scx_enabling_sub_sched
555*daf8e166STejun Heo 	 * must stay set until SUB_INIT is cleared from every marked task -
556*daf8e166STejun Heo 	 * scx_disable_and_exit_task() reads it when a task exits concurrently.
557*daf8e166STejun Heo 	 */
558*daf8e166STejun Heo 	scx_task_iter_start(&sti, sch->cgrp);
559*daf8e166STejun Heo 	while ((p = scx_task_iter_next_locked(&sti))) {
560*daf8e166STejun Heo 		if (p->scx.flags & SCX_TASK_SUB_INIT) {
561*daf8e166STejun Heo 			scx_sub_init_cancel_task(sch, p);
562*daf8e166STejun Heo 			p->scx.flags &= ~SCX_TASK_SUB_INIT;
563*daf8e166STejun Heo 		}
564*daf8e166STejun Heo 	}
565*daf8e166STejun Heo 	scx_task_iter_stop(&sti);
566*daf8e166STejun Heo 	scx_enabling_sub_sched = NULL;
567*daf8e166STejun Heo err_unlock_and_disable:
568*daf8e166STejun Heo 	/* we'll soon enter disable path, keep bypass on */
569*daf8e166STejun Heo 	scx_cgroup_unlock();
570*daf8e166STejun Heo 	percpu_up_write(&scx_fork_rwsem);
571*daf8e166STejun Heo err_disable:
572*daf8e166STejun Heo 	mutex_unlock(&scx_enable_mutex);
573*daf8e166STejun Heo 	scx_flush_disable_work(sch);
574*daf8e166STejun Heo 	cmd->ret = 0;
575*daf8e166STejun Heo }
576*daf8e166STejun Heo 
577*daf8e166STejun Heo static s32 scx_cgroup_lifetime_notify(struct notifier_block *nb,
578*daf8e166STejun Heo 				      unsigned long action, void *data)
579*daf8e166STejun Heo {
580*daf8e166STejun Heo 	struct cgroup *cgrp = data;
581*daf8e166STejun Heo 	struct cgroup *parent = cgroup_parent(cgrp);
582*daf8e166STejun Heo 
583*daf8e166STejun Heo 	if (!cgroup_on_dfl(cgrp))
584*daf8e166STejun Heo 		return NOTIFY_OK;
585*daf8e166STejun Heo 
586*daf8e166STejun Heo 	switch (action) {
587*daf8e166STejun Heo 	case CGROUP_LIFETIME_ONLINE:
588*daf8e166STejun Heo 		/* inherit ->scx_sched from $parent */
589*daf8e166STejun Heo 		if (parent)
590*daf8e166STejun Heo 			rcu_assign_pointer(cgrp->scx_sched, parent->scx_sched);
591*daf8e166STejun Heo 		break;
592*daf8e166STejun Heo 	case CGROUP_LIFETIME_OFFLINE:
593*daf8e166STejun Heo 		/* if there is a sched attached, shoot it down */
594*daf8e166STejun Heo 		if (cgrp->scx_sched && cgrp->scx_sched->cgrp == cgrp)
595*daf8e166STejun Heo 			scx_exit(cgrp->scx_sched, SCX_EXIT_UNREG_KERN,
596*daf8e166STejun Heo 				 SCX_ECODE_RSN_CGROUP_OFFLINE,
597*daf8e166STejun Heo 				 "cgroup %llu going offline", cgroup_id(cgrp));
598*daf8e166STejun Heo 		break;
599*daf8e166STejun Heo 	}
600*daf8e166STejun Heo 
601*daf8e166STejun Heo 	return NOTIFY_OK;
602*daf8e166STejun Heo }
603*daf8e166STejun Heo 
604*daf8e166STejun Heo static struct notifier_block scx_cgroup_lifetime_nb = {
605*daf8e166STejun Heo 	.notifier_call = scx_cgroup_lifetime_notify,
606*daf8e166STejun Heo };
607*daf8e166STejun Heo 
608*daf8e166STejun Heo static s32 __init scx_cgroup_lifetime_notifier_init(void)
609*daf8e166STejun Heo {
610*daf8e166STejun Heo 	return blocking_notifier_chain_register(&cgroup_lifetime_notifier,
611*daf8e166STejun Heo 						&scx_cgroup_lifetime_nb);
612*daf8e166STejun Heo }
613*daf8e166STejun Heo core_initcall(scx_cgroup_lifetime_notifier_init);
614*daf8e166STejun Heo 
615*daf8e166STejun Heo void scx_pstack_recursion_on_dispatch(struct bpf_prog *prog)
616*daf8e166STejun Heo {
617*daf8e166STejun Heo 	struct scx_sched *sch;
618*daf8e166STejun Heo 
619*daf8e166STejun Heo 	guard(rcu)();
620*daf8e166STejun Heo 	sch = scx_prog_sched(prog->aux);
621*daf8e166STejun Heo 	if (unlikely(!sch))
622*daf8e166STejun Heo 		return;
623*daf8e166STejun Heo 
624*daf8e166STejun Heo 	scx_error(sch, "dispatch recursion detected");
625*daf8e166STejun Heo }
626*daf8e166STejun Heo 
627*daf8e166STejun Heo __bpf_kfunc_start_defs();
628*daf8e166STejun Heo 
629*daf8e166STejun Heo /**
630*daf8e166STejun Heo  * scx_bpf_sub_dispatch - Trigger dispatching on a child scheduler
631*daf8e166STejun Heo  * @cgroup_id: cgroup ID of the child scheduler to dispatch
632*daf8e166STejun Heo  * @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
633*daf8e166STejun Heo  *
634*daf8e166STejun Heo  * Allows a parent scheduler to trigger dispatching on one of its direct
635*daf8e166STejun Heo  * child schedulers. The child scheduler runs its dispatch operation to
636*daf8e166STejun Heo  * move tasks from dispatch queues to the local runqueue.
637*daf8e166STejun Heo  *
638*daf8e166STejun Heo  * Returns: true on success, false if cgroup_id is invalid, not a direct
639*daf8e166STejun Heo  * child, or caller lacks dispatch permission.
640*daf8e166STejun Heo  */
641*daf8e166STejun Heo __bpf_kfunc bool scx_bpf_sub_dispatch(u64 cgroup_id, const struct bpf_prog_aux *aux)
642*daf8e166STejun Heo {
643*daf8e166STejun Heo 	struct rq *this_rq = this_rq();
644*daf8e166STejun Heo 	struct scx_sched *parent, *child;
645*daf8e166STejun Heo 
646*daf8e166STejun Heo 	guard(rcu)();
647*daf8e166STejun Heo 	parent = scx_prog_sched(aux);
648*daf8e166STejun Heo 	if (unlikely(!parent))
649*daf8e166STejun Heo 		return false;
650*daf8e166STejun Heo 
651*daf8e166STejun Heo 	child = scx_find_sub_sched(cgroup_id);
652*daf8e166STejun Heo 
653*daf8e166STejun Heo 	if (unlikely(!child))
654*daf8e166STejun Heo 		return false;
655*daf8e166STejun Heo 
656*daf8e166STejun Heo 	if (unlikely(scx_parent(child) != parent)) {
657*daf8e166STejun Heo 		scx_error(parent, "trying to dispatch a distant sub-sched on cgroup %llu",
658*daf8e166STejun Heo 			  cgroup_id);
659*daf8e166STejun Heo 		return false;
660*daf8e166STejun Heo 	}
661*daf8e166STejun Heo 
662*daf8e166STejun Heo 	return scx_dispatch_sched(child, this_rq, this_rq->scx.sub_dispatch_prev,
663*daf8e166STejun Heo 				  true);
664*daf8e166STejun Heo }
665*daf8e166STejun Heo 
666*daf8e166STejun Heo __bpf_kfunc_end_defs();
667*daf8e166STejun Heo 
668*daf8e166STejun Heo #endif	/* CONFIG_EXT_SUB_SCHED */
669