xref: /linux/kernel/sched/ext/sub.c (revision ca3aec453d64d2ad89156c10a2e1b15a93b9fe50)
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  */
16b81a6c01STejun Heo #include <linux/delay.h>
17daf8e166STejun Heo #include <linux/rhashtable.h>
18daf8e166STejun Heo #include "internal.h"
19daf8e166STejun Heo #include "cid.h"
20daf8e166STejun Heo #include "arena.h"
21daf8e166STejun Heo #include "sub.h"
22daf8e166STejun Heo 
23daf8e166STejun Heo #ifdef CONFIG_EXT_SUB_SCHED
24daf8e166STejun Heo 
25daf8e166STejun Heo /**
26bbda59d8STejun Heo  * scx_skip_subtree_pre - Skip @pos's subtree in a pre-order walk
27bbda59d8STejun Heo  * @pos: current position
28bbda59d8STejun Heo  * @root: walk root
29bbda59d8STejun Heo  *
30bbda59d8STejun Heo  * In a walk started by scx_next_descendant_pre(), continue past @pos's subtree:
31bbda59d8STejun Heo  * return @pos's next sibling, or the closest ancestor's next sibling, or NULL
32bbda59d8STejun Heo  * if @pos's subtree is the last under @root. Same locking rules.
33bbda59d8STejun Heo  */
34bbda59d8STejun Heo struct scx_sched *scx_skip_subtree_pre(struct scx_sched *pos, struct scx_sched *root)
35bbda59d8STejun Heo {
36bbda59d8STejun Heo 	struct scx_sched *next;
37bbda59d8STejun Heo 
38bbda59d8STejun Heo 	lockdep_assert(lockdep_is_held(&scx_enable_mutex) ||
39bbda59d8STejun Heo 		       lockdep_is_held(&scx_sched_lock) ||
40bbda59d8STejun Heo 		       rcu_read_lock_any_held());
41bbda59d8STejun Heo 
42bbda59d8STejun Heo 	while (pos != root) {
43bbda59d8STejun Heo 		next = list_next_or_null_rcu(&scx_parent(pos)->children, &pos->sibling,
44bbda59d8STejun Heo 					     struct scx_sched, sibling);
45bbda59d8STejun Heo 		if (next)
46bbda59d8STejun Heo 			return next;
47bbda59d8STejun Heo 		pos = scx_parent(pos);
48bbda59d8STejun Heo 	}
49bbda59d8STejun Heo 	return NULL;
50bbda59d8STejun Heo }
51bbda59d8STejun Heo 
52bbda59d8STejun Heo /**
53daf8e166STejun Heo  * scx_next_descendant_pre - find the next descendant for pre-order walk
54daf8e166STejun Heo  * @pos: the current position (%NULL to initiate traversal)
55daf8e166STejun Heo  * @root: sched whose descendants to walk
56daf8e166STejun Heo  *
57daf8e166STejun Heo  * To be used by scx_for_each_descendant_pre(). Find the next descendant to
58daf8e166STejun Heo  * visit for pre-order traversal of @root's descendants. @root is included in
59daf8e166STejun Heo  * the iteration and the first node to be visited.
60daf8e166STejun Heo  */
61daf8e166STejun Heo struct scx_sched *scx_next_descendant_pre(struct scx_sched *pos, struct scx_sched *root)
62daf8e166STejun Heo {
63daf8e166STejun Heo 	struct scx_sched *next;
64daf8e166STejun Heo 
65daf8e166STejun Heo 	lockdep_assert(lockdep_is_held(&scx_enable_mutex) ||
6670f8b178STejun Heo 		       lockdep_is_held(&scx_sched_lock) ||
6770f8b178STejun Heo 		       rcu_read_lock_any_held());
68daf8e166STejun Heo 
69daf8e166STejun Heo 	/* if first iteration, visit @root */
70daf8e166STejun Heo 	if (!pos)
71daf8e166STejun Heo 		return root;
72daf8e166STejun Heo 
73daf8e166STejun Heo 	/* visit the first child if exists */
7470f8b178STejun Heo 	next = list_first_or_null_rcu(&pos->children, struct scx_sched, sibling);
75daf8e166STejun Heo 	if (next)
76daf8e166STejun Heo 		return next;
77daf8e166STejun Heo 
78daf8e166STejun Heo 	/* no child, visit my or the closest ancestor's next sibling */
79bbda59d8STejun Heo 	return scx_skip_subtree_pre(pos, root);
80daf8e166STejun Heo }
81daf8e166STejun Heo 
82daf8e166STejun Heo static struct scx_sched *scx_find_sub_sched(u64 cgroup_id)
83daf8e166STejun Heo {
84daf8e166STejun Heo 	return rhashtable_lookup(&scx_sched_hash, &cgroup_id,
85daf8e166STejun Heo 				 scx_sched_hash_params);
86daf8e166STejun Heo }
87daf8e166STejun Heo 
88daf8e166STejun Heo void scx_set_task_sched(struct task_struct *p, struct scx_sched *sch)
89daf8e166STejun Heo {
90daf8e166STejun Heo 	rcu_assign_pointer(p->scx.sched, sch);
91daf8e166STejun Heo }
92daf8e166STejun Heo 
93daf8e166STejun Heo struct cgroup *sch_cgroup(struct scx_sched *sch)
94daf8e166STejun Heo {
95daf8e166STejun Heo 	return sch->cgrp;
96daf8e166STejun Heo }
97daf8e166STejun Heo 
98daf8e166STejun Heo /* for each descendant of @cgrp including self, set ->scx_sched to @sch */
99daf8e166STejun Heo void set_cgroup_sched(struct cgroup *cgrp, struct scx_sched *sch)
100daf8e166STejun Heo {
101daf8e166STejun Heo 	struct cgroup *pos;
102daf8e166STejun Heo 	struct cgroup_subsys_state *css;
103daf8e166STejun Heo 
104daf8e166STejun Heo 	cgroup_for_each_live_descendant_pre(pos, css, cgrp)
105daf8e166STejun Heo 		rcu_assign_pointer(pos->scx_sched, sch);
106daf8e166STejun Heo }
107daf8e166STejun Heo 
1088dba3bbdSTejun Heo static void free_pshard(struct scx_pshard *pshard)
1098dba3bbdSTejun Heo {
1105f2a9a4cSTejun Heo 	struct scx_caps_updated *cu;
1115f2a9a4cSTejun Heo 
1125f2a9a4cSTejun Heo 	if (!pshard)
1135f2a9a4cSTejun Heo 		return;
1145f2a9a4cSTejun Heo 	cu = &pshard->caps_updated;
1155f2a9a4cSTejun Heo 	if (cu->cmask_arena_out)
1165f2a9a4cSTejun Heo 		scx_arena_free(pshard->sch, cu->cmask_arena_out,
1175f2a9a4cSTejun Heo 			       struct_size_t(struct scx_cmask, bits,
1185f2a9a4cSTejun Heo 					     SCX_CMASK_NR_WORDS(pshard->nr_cids)));
1198dba3bbdSTejun Heo 	kfree(pshard);
1208dba3bbdSTejun Heo }
1218dba3bbdSTejun Heo 
1228dba3bbdSTejun Heo void scx_free_pshards(struct scx_sched *sch)
1238dba3bbdSTejun Heo {
1248dba3bbdSTejun Heo 	s32 si;
1258dba3bbdSTejun Heo 
1268dba3bbdSTejun Heo 	if (!sch->pshard)
1278dba3bbdSTejun Heo 		return;
1288dba3bbdSTejun Heo 	for (si = 0; si < sch->nr_pshards; si++)
1298dba3bbdSTejun Heo 		free_pshard(sch->pshard[si]);
1308dba3bbdSTejun Heo 	kfree(sch->pshard);
1318dba3bbdSTejun Heo }
1328dba3bbdSTejun Heo 
1338dba3bbdSTejun Heo static struct scx_pshard *alloc_pshard(struct scx_sched *sch, s32 shard_idx, s32 node)
1348dba3bbdSTejun Heo {
13586094b95STejun Heo 	const struct scx_cid_shard *shard = &scx_cid_shard_ranges[shard_idx];
1365f2a9a4cSTejun Heo 	size_t cmask_size = struct_size_t(struct scx_cmask, bits,
1375f2a9a4cSTejun Heo 					  SCX_CMASK_NR_WORDS(shard->nr_cids));
13886094b95STejun Heo 	struct scx_pshard *pshard;
1395f2a9a4cSTejun Heo 	struct scx_caps_updated *cu;
14086094b95STejun Heo 	s32 i;
14186094b95STejun Heo 
14286094b95STejun Heo 	pshard = kzalloc_node(sizeof(*pshard), GFP_KERNEL, node);
14386094b95STejun Heo 	if (!pshard)
14486094b95STejun Heo 		return NULL;
14586094b95STejun Heo 
14686094b95STejun Heo 	raw_spin_lock_init(&pshard->lock);
14786094b95STejun Heo 	pshard->sch = sch;
1485f2a9a4cSTejun Heo 	pshard->base = shard->base_cid;
1495f2a9a4cSTejun Heo 	pshard->nr_cids = shard->nr_cids;
15086094b95STejun Heo 
15186094b95STejun Heo 	for (i = 0; i < __SCX_NR_CAPS; i++)
15286094b95STejun Heo 		scx_cmask_init(&pshard->caps[i].cmask, shard->base_cid, shard->nr_cids);
15386094b95STejun Heo 
1545f2a9a4cSTejun Heo 	cu = &pshard->caps_updated;
1555f2a9a4cSTejun Heo 	raw_spin_lock_init(&cu->lock);
1565f2a9a4cSTejun Heo 	INIT_LIST_HEAD(&cu->node_in_flight);
1575f2a9a4cSTejun Heo 	__scx_cmask_init(&cu->cmask, shard->base_cid, shard->nr_cids, SCX_CID_SHARD_MAX_CPUS);
1585f2a9a4cSTejun Heo 
1595f2a9a4cSTejun Heo 	cu->cmask_arena_out = scx_arena_alloc(sch, cmask_size);
1605f2a9a4cSTejun Heo 	if (!cu->cmask_arena_out) {
1615f2a9a4cSTejun Heo 		free_pshard(pshard);
1625f2a9a4cSTejun Heo 		return NULL;
1635f2a9a4cSTejun Heo 	}
1645f2a9a4cSTejun Heo 
1655f2a9a4cSTejun Heo 	scx_cmask_init(cu->cmask_arena_out, shard->base_cid, shard->nr_cids);
1665f2a9a4cSTejun Heo 
16786094b95STejun Heo 	return pshard;
1688dba3bbdSTejun Heo }
1698dba3bbdSTejun Heo 
1708dba3bbdSTejun Heo s32 scx_alloc_pshards(struct scx_sched *sch)
1718dba3bbdSTejun Heo {
1728dba3bbdSTejun Heo 	struct scx_pshard **pshard;
1738dba3bbdSTejun Heo 	s32 si;
1748dba3bbdSTejun Heo 
1758dba3bbdSTejun Heo 	if (!sch->is_cid_type || !sch->arena_pool)
1768dba3bbdSTejun Heo 		return 0;
1778dba3bbdSTejun Heo 
1788dba3bbdSTejun Heo 	pshard = kzalloc_objs(pshard[0], scx_nr_cid_shards, GFP_KERNEL);
1798dba3bbdSTejun Heo 	if (!pshard)
1808dba3bbdSTejun Heo 		return -ENOMEM;
1818dba3bbdSTejun Heo 
1828dba3bbdSTejun Heo 	for (si = 0; si < scx_nr_cid_shards; si++) {
1838dba3bbdSTejun Heo 		pshard[si] = alloc_pshard(sch, si, scx_shard_node[si]);
1848dba3bbdSTejun Heo 		if (!pshard[si]) {
1858dba3bbdSTejun Heo 			while (--si >= 0)
1868dba3bbdSTejun Heo 				free_pshard(pshard[si]);
1878dba3bbdSTejun Heo 			kfree(pshard);
1888dba3bbdSTejun Heo 			return -ENOMEM;
1898dba3bbdSTejun Heo 		}
1908dba3bbdSTejun Heo 	}
1918dba3bbdSTejun Heo 
1928dba3bbdSTejun Heo 	sch->nr_pshards = scx_nr_cid_shards;
1938dba3bbdSTejun Heo 	/*
1948dba3bbdSTejun Heo 	 * Publish only after every entry is built so a reader observing
1958dba3bbdSTejun Heo 	 * @sch->pshard never sees a partially-filled array. Pair the store
1968dba3bbdSTejun Heo 	 * with a barrier and READ_ONCE() on the read side.
1978dba3bbdSTejun Heo 	 */
1988dba3bbdSTejun Heo 	smp_wmb();
1998dba3bbdSTejun Heo 	WRITE_ONCE(sch->pshard, pshard);
2008dba3bbdSTejun Heo 	return 0;
2018dba3bbdSTejun Heo }
2028dba3bbdSTejun Heo 
20386094b95STejun Heo /*
20486094b95STejun Heo  * Seed the root's caps fully. Root owns all cids on all caps at enable time.
20586094b95STejun Heo  * Children acquire caps via scx_bpf_sub_grant().
20686094b95STejun Heo  */
20786094b95STejun Heo void scx_init_root_caps(struct scx_sched *sch)
20886094b95STejun Heo {
20986094b95STejun Heo 	s32 si, i;
21086094b95STejun Heo 
21186094b95STejun Heo 	for (si = 0; si < sch->nr_pshards; si++) {
21286094b95STejun Heo 		struct scx_pshard *ps = sch->pshard[si];
21386094b95STejun Heo 
21486094b95STejun Heo 		for (i = 0; i < __SCX_NR_CAPS; i++)
21586094b95STejun Heo 			scx_cmask_fill(&ps->caps[i].cmask);
21686094b95STejun Heo 	}
21786094b95STejun Heo }
21886094b95STejun Heo 
21975a8c820STejun Heo /**
22075a8c820STejun Heo  * scx_local_or_reject_dsq - Pick the local or reject DSQ for an insert
22175a8c820STejun Heo  * @sch: enqueuing sub-sched
22275a8c820STejun Heo  * @rq: rq whose local DSQ @p targets
22375a8c820STejun Heo  * @p: task being inserted
2246ea3be36STejun Heo  * @enq_flags: in/out, unhonored flags are cleared
22575a8c820STejun Heo  *
22675a8c820STejun Heo  * Return @rq's local DSQ if @sch holds the required caps on @rq's cid,
22775a8c820STejun Heo  * otherwise @rq's reject DSQ after recording the reenq reason on @p.
22875a8c820STejun Heo  *
2296ea3be36STejun Heo  * %SCX_ENQ_IMMED and %SCX_ENQ_PREEMPT are cleared when diverting to reject.
2306ea3be36STejun Heo  * %SCX_ENQ_PREEMPT is also cleared on a fallback migration-disabled admission.
2316ea3be36STejun Heo  *
23275a8c820STejun Heo  * Bypass doesn't need special-casing as a bypassing sched's tasks are enqueued
23375a8c820STejun Heo  * to and run by its nearest non-bypassing ancestor. If root is bypassing, it
23475a8c820STejun Heo  * always holds all caps.
23575a8c820STejun Heo  */
23675a8c820STejun Heo struct scx_dispatch_q *scx_local_or_reject_dsq(struct scx_sched *sch, struct rq *rq,
23775a8c820STejun Heo 					       struct task_struct *p, u64 *enq_flags)
23875a8c820STejun Heo {
23975a8c820STejun Heo 	s32 cid = __scx_cpu_to_cid(cpu_of(rq));
240f2c9f515STejun Heo 	struct scx_sched *asch = rq->scx.remote_activate_sch ?: sch;
2416ea3be36STejun Heo 	u64 needed = scx_caps_for_enq(*enq_flags);
2426ea3be36STejun Heo 	u64 missing;
2436ea3be36STejun Heo 
244f2c9f515STejun Heo 	/*
245f2c9f515STejun Heo 	 * On a remote activation the scheduling sched (@asch) differs from
246f2c9f515STejun Heo 	 * @p's owner (@sch). Check caps against the scheduling sched.
247f2c9f515STejun Heo 	 */
2486ea3be36STejun Heo 	if (*enq_flags & SCX_ENQ_PREEMPT)
249f2c9f515STejun Heo 		needed |= scx_caps_for_preempt(asch, rq);
250f2c9f515STejun Heo 	missing = scx_missing_caps(asch, cpu_of(rq), needed);
25175a8c820STejun Heo 
25275a8c820STejun Heo 	/* requirements met */
25375a8c820STejun Heo 	if (likely(!missing))
25475a8c820STejun Heo 		return &rq->scx.local_dsq;
25575a8c820STejun Heo 
25675a8c820STejun Heo 	/*
25775a8c820STejun Heo 	 * The task must run on this CPU regardless of caps: the rq is draining
25875a8c820STejun Heo 	 * offline (BPF scheduler bypassed), the task is migration-disabled, or a
25975a8c820STejun Heo 	 * migration is pending. Admit despite the missing caps and count it.
2606ea3be36STejun Heo 	 * Refuse preemptions.
26175a8c820STejun Heo 	 */
26275a8c820STejun Heo 	if (unlikely(!scx_rq_online(rq) || is_migration_disabled(p) ||
26375a8c820STejun Heo 		     p->migration_pending)) {
26475a8c820STejun Heo 		__scx_add_event(sch, SCX_EV_SUB_FORCED_ADMIT, 1);
2656ea3be36STejun Heo 		*enq_flags &= ~SCX_ENQ_PREEMPT;
26675a8c820STejun Heo 		return &rq->scx.local_dsq;
26775a8c820STejun Heo 	}
26875a8c820STejun Heo 
26975a8c820STejun Heo 	p->scx.reenq_reason_caps = missing;
27075a8c820STejun Heo 	p->scx.reenq_reason_cid = cid;
27175a8c820STejun Heo 
27275a8c820STejun Heo 	/*
27375a8c820STejun Heo 	 * Only local DSQ can honor IMMED and dsq_inc_nr() WARNs on IMMED into
27475a8c820STejun Heo 	 * others. Strip both the enq flag and the sticky task flag - the
2756ea3be36STejun Heo 	 * latter can carry in from an earlier admitted IMMED insert. Strip
2766ea3be36STejun Heo 	 * PREEMPT too.
27775a8c820STejun Heo 	 */
2786ea3be36STejun Heo 	*enq_flags &= ~(SCX_ENQ_IMMED | SCX_ENQ_PREEMPT);
27975a8c820STejun Heo 	p->scx.flags &= ~SCX_TASK_IMMED;
28075a8c820STejun Heo 
28175a8c820STejun Heo 	return &rq->scx.reject_dsq;
28275a8c820STejun Heo }
28375a8c820STejun Heo 
28475a8c820STejun Heo /* @p lost the caps needed to stay on @rq's local DSQ? Record reason if so. */
28575a8c820STejun Heo bool scx_task_reenq_on_cap_revoke(struct rq *rq, struct task_struct *p)
28675a8c820STejun Heo {
28775a8c820STejun Heo 	u64 missing;
28875a8c820STejun Heo 
28975a8c820STejun Heo 	/* migration-disabled tasks are admitted regardless of caps */
29075a8c820STejun Heo 	if (is_migration_disabled(p))
29175a8c820STejun Heo 		return false;
29275a8c820STejun Heo 
29375a8c820STejun Heo 	missing = scx_missing_caps(scx_task_sched(p), cpu_of(rq), scx_caps_for_task(p));
29475a8c820STejun Heo 	if (likely(!missing))
29575a8c820STejun Heo 		return false;
29675a8c820STejun Heo 
29775a8c820STejun Heo 	p->scx.reenq_reason_caps = missing;
29875a8c820STejun Heo 	p->scx.reenq_reason_cid = __scx_cpu_to_cid(cpu_of(rq));
29975a8c820STejun Heo 	return true;
30075a8c820STejun Heo }
30175a8c820STejun Heo 
30275a8c820STejun Heo /*
30375a8c820STejun Heo  * Drain @rq->scx.reject_dsq, reenqueueing each task so the BPF re-decides
30475a8c820STejun Heo  * from p->scx.reenq_reason_*.
30575a8c820STejun Heo  *
30675a8c820STejun Heo  * A task can be re-rejected repeatedly, and there's no repeat limit here.
30775a8c820STejun Heo  * Rejection can't happen for root, and sub-scheds can be safely ejected after
30875a8c820STejun Heo  * triggering the stall watchdog.
30975a8c820STejun Heo  */
31075a8c820STejun Heo void scx_reenq_reject(struct rq *rq)
31175a8c820STejun Heo {
31275a8c820STejun Heo 	LIST_HEAD(tasks);
31375a8c820STejun Heo 	struct task_struct *p, *n;
31475a8c820STejun Heo 
31575a8c820STejun Heo 	lockdep_assert_rq_held(rq);
31675a8c820STejun Heo 
31775a8c820STejun Heo 	if (list_empty(&rq->scx.reject_dsq.list))
31875a8c820STejun Heo 		return;
31975a8c820STejun Heo 
32075a8c820STejun Heo 	/*
32175a8c820STejun Heo 	 * Move to a private list so a task re-rejected by the
32275a8c820STejun Heo 	 * scx_do_enqueue_task() below isn't revisited this round.
32375a8c820STejun Heo 	 */
32475a8c820STejun Heo 	list_for_each_entry_safe(p, n, &rq->scx.reject_dsq.list, scx.dsq_list.node) {
32575a8c820STejun Heo 		/* migration_pending tasks should have bypassed to local DSQ */
32675a8c820STejun Heo 		if (WARN_ON_ONCE(p->migration_pending))
32775a8c820STejun Heo 			continue;
32875a8c820STejun Heo 
32975a8c820STejun Heo 		scx_dispatch_dequeue(rq, p);
33075a8c820STejun Heo 
33175a8c820STejun Heo 		if (WARN_ON_ONCE(p->scx.flags & SCX_TASK_REENQ_REASON_MASK))
33275a8c820STejun Heo 			p->scx.flags &= ~SCX_TASK_REENQ_REASON_MASK;
33375a8c820STejun Heo 		p->scx.flags |= SCX_TASK_REENQ_CAP;
33475a8c820STejun Heo 
33575a8c820STejun Heo 		list_add_tail(&p->scx.dsq_list.node, &tasks);
33675a8c820STejun Heo 	}
33775a8c820STejun Heo 
33875a8c820STejun Heo 	list_for_each_entry_safe(p, n, &tasks, scx.dsq_list.node) {
33975a8c820STejun Heo 		list_del_init(&p->scx.dsq_list.node);
34075a8c820STejun Heo 
34175a8c820STejun Heo 		scx_do_enqueue_task(rq, p, SCX_ENQ_REENQ, -1);
34275a8c820STejun Heo 
34375a8c820STejun Heo 		p->scx.flags &= ~SCX_TASK_REENQ_REASON_MASK;
34475a8c820STejun Heo 	}
34575a8c820STejun Heo }
34675a8c820STejun Heo 
3475f2a9a4cSTejun Heo /* record a caps change, see struct scx_caps_updated */
3485f2a9a4cSTejun Heo static void caps_updated_record(struct scx_pshard *ps, const struct scx_cmask *cids, u64 caps,
3495f2a9a4cSTejun Heo 				struct list_head *to_deliver)
3505f2a9a4cSTejun Heo {
3515f2a9a4cSTejun Heo 	struct scx_caps_updated *cu = &ps->caps_updated;
3525f2a9a4cSTejun Heo 
3535f2a9a4cSTejun Heo 	guard(raw_spinlock)(&cu->lock);
3545f2a9a4cSTejun Heo 	scx_cmask_or(&cu->cmask, cids);
3555f2a9a4cSTejun Heo 	cu->caps |= caps;
3565f2a9a4cSTejun Heo 	if (list_empty(&cu->node_in_flight))
3575f2a9a4cSTejun Heo 		list_add_tail(&cu->node_in_flight, to_deliver);
3585f2a9a4cSTejun Heo }
3595f2a9a4cSTejun Heo 
3605f2a9a4cSTejun Heo /* deliver queued caps_updated callbacks, see struct scx_caps_updated */
3615f2a9a4cSTejun Heo static void caps_updated_deliver(struct list_head *to_deliver)
3625f2a9a4cSTejun Heo {
3635f2a9a4cSTejun Heo 	struct scx_caps_updated *cu, *tmp;
3645f2a9a4cSTejun Heo 
3655f2a9a4cSTejun Heo 	list_for_each_entry_safe(cu, tmp, to_deliver, node_in_flight) {
3665f2a9a4cSTejun Heo 		struct scx_pshard *ps = container_of(cu, struct scx_pshard, caps_updated);
3675f2a9a4cSTejun Heo 		struct scx_sched *sch = ps->sch;
3685f2a9a4cSTejun Heo 
3695f2a9a4cSTejun Heo 		while (true) {
3705f2a9a4cSTejun Heo 			u64 caps = 0;
3715f2a9a4cSTejun Heo 
3725f2a9a4cSTejun Heo 			/*
3735f2a9a4cSTejun Heo 			 * During enable, has_op is set after ops.sub_attach(),
3745f2a9a4cSTejun Heo 			 * so !has_op means the op is absent or the sched isn't
3755f2a9a4cSTejun Heo 			 * live yet - e.g. caps grant from ops.sub_attach().
3765f2a9a4cSTejun Heo 			 * Either way don't consume - leave for
3775f2a9a4cSTejun Heo 			 * scx_sub_seed_caps() to deliver once live.
3785f2a9a4cSTejun Heo 			 */
3795f2a9a4cSTejun Heo 			scoped_guard (raw_spinlock, &cu->lock) {
3805f2a9a4cSTejun Heo 				if (cu->caps && SCX_HAS_OP(sch, sub_caps_updated) &&
3815f2a9a4cSTejun Heo 				    likely(!READ_ONCE(sch->aborting))) {
3825f2a9a4cSTejun Heo 					struct scx_cmask_ref ref;
3835f2a9a4cSTejun Heo 
3845f2a9a4cSTejun Heo 					caps = cu->caps;
3855f2a9a4cSTejun Heo 					scx_cmask_ref_init_kern(sch, cu->cmask_arena_out,
3865f2a9a4cSTejun Heo 								ps->base, ps->nr_cids, &ref);
3875f2a9a4cSTejun Heo 					scx_cmask_ref_copy(&ref, &cu->cmask);
3885f2a9a4cSTejun Heo 					scx_cmask_clear(&cu->cmask);
3895f2a9a4cSTejun Heo 					cu->caps = 0;
3905f2a9a4cSTejun Heo 				} else {
3915f2a9a4cSTejun Heo 					list_del_init(&cu->node_in_flight);
3925f2a9a4cSTejun Heo 				}
3935f2a9a4cSTejun Heo 			}
3945f2a9a4cSTejun Heo 			if (!caps)
3955f2a9a4cSTejun Heo 				break;
3965f2a9a4cSTejun Heo 
3975f2a9a4cSTejun Heo 			/* caps != 0 only when deliverable (has_op, above) */
3985f2a9a4cSTejun Heo 			SCX_CALL_OP(sch, sub_caps_updated, NULL,
3995f2a9a4cSTejun Heo 				    scx_kaddr_to_arena(sch, cu->cmask_arena_out),
4005f2a9a4cSTejun Heo 				    caps);
4015f2a9a4cSTejun Heo 		}
4025f2a9a4cSTejun Heo 	}
4035f2a9a4cSTejun Heo }
4045f2a9a4cSTejun Heo 
4055f2a9a4cSTejun Heo /*
4065f2a9a4cSTejun Heo  * Deliver caps owed to @sch that couldn't be delivered earlier (e.g. a grant
4075f2a9a4cSTejun Heo  * taken during its sub_attach(), before has_op was set). Called once @sch is
4085f2a9a4cSTejun Heo  * enabled.
4095f2a9a4cSTejun Heo  */
4105f2a9a4cSTejun Heo static void scx_sub_seed_caps(struct scx_sched *sch)
4115f2a9a4cSTejun Heo {
4125f2a9a4cSTejun Heo 	LIST_HEAD(to_deliver);
4135f2a9a4cSTejun Heo 	s32 si;
4145f2a9a4cSTejun Heo 
4155f2a9a4cSTejun Heo 	guard(irqsave)();
4165f2a9a4cSTejun Heo 
4175f2a9a4cSTejun Heo 	for (si = 0; si < sch->nr_pshards; si++) {
4185f2a9a4cSTejun Heo 		struct scx_pshard *ps = sch->pshard[si];
4195f2a9a4cSTejun Heo 		struct scx_caps_updated *cu = &ps->caps_updated;
4205f2a9a4cSTejun Heo 
4215f2a9a4cSTejun Heo 		scoped_guard (raw_spinlock, &cu->lock) {
4225f2a9a4cSTejun Heo 			if (cu->caps && list_empty(&cu->node_in_flight))
4235f2a9a4cSTejun Heo 				list_add_tail(&cu->node_in_flight, &to_deliver);
4245f2a9a4cSTejun Heo 		}
4255f2a9a4cSTejun Heo 	}
4265f2a9a4cSTejun Heo 	caps_updated_deliver(&to_deliver);
4275f2a9a4cSTejun Heo }
4285f2a9a4cSTejun Heo 
42956fdc35bSTejun Heo static u64 calc_effective_caps(struct scx_pshard *ps, s32 cid)
43056fdc35bSTejun Heo {
43156fdc35bSTejun Heo 	u64 ecaps = 0;
43256fdc35bSTejun Heo 	u32 cap_bit;
43356fdc35bSTejun Heo 
43456fdc35bSTejun Heo 	for (cap_bit = 0; cap_bit < __SCX_NR_CAPS; cap_bit++)
43556fdc35bSTejun Heo 		if (scx_cmask_test(cid, &ps->caps[cap_bit].cmask))
43656fdc35bSTejun Heo 			ecaps |= BIT_U64(cap_bit) | scx_caps_implied(BIT_U64(cap_bit));
43756fdc35bSTejun Heo 	return ecaps;
43856fdc35bSTejun Heo }
43956fdc35bSTejun Heo 
44056fdc35bSTejun Heo /**
44156fdc35bSTejun Heo  * queue_sync_ecaps - Queue ecaps update for a (sch, cid) pair
44256fdc35bSTejun Heo  * @sch: sched to update
44356fdc35bSTejun Heo  * @cid: cid to update
44456fdc35bSTejun Heo  *
44556fdc35bSTejun Heo  * Queue an ecaps update for @sch's @cid and kick the cpu so that it syncs in
44656fdc35bSTejun Heo  * balance_one().
44756fdc35bSTejun Heo  */
44856fdc35bSTejun Heo static void queue_sync_ecaps(struct scx_sched *sch, s32 cid)
44956fdc35bSTejun Heo {
45056fdc35bSTejun Heo 	s32 cpu = __scx_cid_to_cpu(cid);
45156fdc35bSTejun Heo 	struct scx_sched_pcpu *pcpu = per_cpu_ptr(sch->pcpu, cpu);
45256fdc35bSTejun Heo 
45356fdc35bSTejun Heo 	/*
45456fdc35bSTejun Heo 	 * Pairs with smp_mb() in scx_process_sync_ecaps(). Either the check
45556fdc35bSTejun Heo 	 * below sees the node off the list and queues it, or the in-flight sync
45656fdc35bSTejun Heo 	 * sees the caps[] update made before this call.
45756fdc35bSTejun Heo 	 */
45856fdc35bSTejun Heo 	smp_mb();
45956fdc35bSTejun Heo 
46056fdc35bSTejun Heo 	/* @cid's pshard->lock excludes concurrent queueing attempts */
46156fdc35bSTejun Heo 	if (llist_on_list(&pcpu->ecaps_to_sync_node))
46256fdc35bSTejun Heo 		return;
46356fdc35bSTejun Heo 	if (llist_add(&pcpu->ecaps_to_sync_node, &cpu_rq(cpu)->scx.ecaps_to_sync))
46456fdc35bSTejun Heo 		scx_kick_cpu(scx_root, cpu, 0);
46556fdc35bSTejun Heo }
46656fdc35bSTejun Heo 
46756fdc35bSTejun Heo /* discard @rq's queued ecaps syncs */
46856fdc35bSTejun Heo static void discard_queued_syncs(struct rq *rq)
46956fdc35bSTejun Heo {
47056fdc35bSTejun Heo 	struct llist_node *pos, *tmp;
47156fdc35bSTejun Heo 
47256fdc35bSTejun Heo 	lockdep_assert_rq_held(rq);
47356fdc35bSTejun Heo 
47456fdc35bSTejun Heo 	llist_for_each_safe(pos, tmp, llist_del_all(&rq->scx.ecaps_to_sync))
47556fdc35bSTejun Heo 		init_llist_node(pos);
47656fdc35bSTejun Heo }
47756fdc35bSTejun Heo 
47856fdc35bSTejun Heo /**
47956fdc35bSTejun Heo  * scx_process_sync_ecaps - Sync this cpu's ecaps to pshard->caps[]
48056fdc35bSTejun Heo  * @rq: the cid's cpu rq
481b81a6c01STejun Heo  * @prev: @rq's previous task from the in-progress balance
48256fdc35bSTejun Heo  *
48356fdc35bSTejun Heo  * pshard->caps[] is the target configuration. pcpu->ecaps is the effective
48456fdc35bSTejun Heo  * transposed copy owned by the cid's cpu and written only here under @rq's
48556fdc35bSTejun Heo  * lock.
486*ca3aec45STejun Heo  *
487*ca3aec45STejun Heo  * A sched that newly gains baseline access here is owed an update_idle() so it
488*ca3aec45STejun Heo  * learns the cid's idle state. Such a gain arms the per-rq
489*ca3aec45STejun Heo  * %SCX_RQ_SUB_IDLE_RENOTIFY gate so the next idle pick delivers it.
49056fdc35bSTejun Heo  */
491b81a6c01STejun Heo void scx_process_sync_ecaps(struct rq *rq, struct task_struct *prev)
49256fdc35bSTejun Heo {
493b81a6c01STejun Heo 	s32 cpu = cpu_of(rq);
494b81a6c01STejun Heo 	s32 cid, shard;
49556fdc35bSTejun Heo 	struct llist_node *batch, *pos, *tmp;
49675a8c820STejun Heo 	u64 lost_all = 0;
49756fdc35bSTejun Heo 
49856fdc35bSTejun Heo 	lockdep_assert_rq_held(rq);
49956fdc35bSTejun Heo 
50056fdc35bSTejun Heo 	if (likely(llist_empty(&rq->scx.ecaps_to_sync)))
50156fdc35bSTejun Heo 		return;
50256fdc35bSTejun Heo 
503b81a6c01STejun Heo 	/*
504b81a6c01STejun Heo 	 * ecaps are zeroed while the cpu is inactive and must stay zero.
505b81a6c01STejun Heo 	 * Discard queued syncs instead of processing them - the
506b81a6c01STejun Heo 	 * scx_online_ecaps() reseed re-syncs every sched on activation.
507b81a6c01STejun Heo 	 * cpu_active() clears before the offline zeroing and sets before the
508b81a6c01STejun Heo 	 * reseed is queued, so this test can neither miss a racing sync nor
509b81a6c01STejun Heo 	 * eat the reseed.
510b81a6c01STejun Heo 	 */
511b81a6c01STejun Heo 	if (unlikely(!cpu_active(cpu))) {
512b81a6c01STejun Heo 		discard_queued_syncs(rq);
513b81a6c01STejun Heo 		return;
514b81a6c01STejun Heo 	}
515b81a6c01STejun Heo 
516b81a6c01STejun Heo 	/* @cid is valid here: the cpu is active with queued syncs */
517b81a6c01STejun Heo 	cid = __scx_cpu_to_cid(cpu);
518b81a6c01STejun Heo 	shard = scx_cid_to_shard[cid];
519b81a6c01STejun Heo 
52056fdc35bSTejun Heo 	batch = llist_del_all(&rq->scx.ecaps_to_sync);
52156fdc35bSTejun Heo 	llist_for_each_safe(pos, tmp, batch) {
52256fdc35bSTejun Heo 		struct scx_sched_pcpu *pcpu =
52356fdc35bSTejun Heo 			container_of(pos, struct scx_sched_pcpu, ecaps_to_sync_node);
52456fdc35bSTejun Heo 		struct scx_pshard *ps = pcpu->sch->pshard[shard];
525*ca3aec45STejun Heo 		u64 old, ecaps, lost, gained;
52656fdc35bSTejun Heo 
52756fdc35bSTejun Heo 		init_llist_node(pos);
52856fdc35bSTejun Heo 
52956fdc35bSTejun Heo 		/* pairs with smp_mb() in queue_sync_ecaps(), see there */
53056fdc35bSTejun Heo 		smp_mb();
53156fdc35bSTejun Heo 
53275a8c820STejun Heo 		old = READ_ONCE(pcpu->ecaps);
533b81a6c01STejun Heo 		ecaps = calc_effective_caps(ps, cid);
534b81a6c01STejun Heo 		WRITE_ONCE(pcpu->ecaps, ecaps);
535b81a6c01STejun Heo 
53675a8c820STejun Heo 		lost = old & ~ecaps;
537*ca3aec45STejun Heo 		gained = ecaps & ~old;
53875a8c820STejun Heo 		lost_all |= lost;
53975a8c820STejun Heo 
540b81a6c01STejun Heo 		/* tell the sched its effective caps on this cid changed */
541b81a6c01STejun Heo 		if (ecaps != pcpu->reported_ecaps &&
542b81a6c01STejun Heo 		    SCX_HAS_OP(pcpu->sch, sub_ecaps_updated) &&
543b81a6c01STejun Heo 		    !scx_bypassing(pcpu->sch, cpu)) {
544b81a6c01STejun Heo 			struct scx_dsp_ctx *dspc = &pcpu->dsp_ctx;
545b81a6c01STejun Heo 
546b81a6c01STejun Heo 			dspc->rq = rq;
547b81a6c01STejun Heo 			/* stash @prev so nested dispatches can access it */
548b81a6c01STejun Heo 			rq->scx.sub_dispatch_prev = prev;
549b81a6c01STejun Heo 			SCX_CALL_OP(pcpu->sch, sub_ecaps_updated, rq, scx_cpu_arg(cpu),
550b81a6c01STejun Heo 				    pcpu->reported_ecaps, ecaps);
551b81a6c01STejun Heo 			rq->scx.sub_dispatch_prev = NULL;
552b81a6c01STejun Heo 			scx_flush_dispatch_buf(pcpu->sch, rq);
553b81a6c01STejun Heo 			pcpu->reported_ecaps = ecaps;
554b81a6c01STejun Heo 		}
555*ca3aec45STejun Heo 
556*ca3aec45STejun Heo 		/*
557*ca3aec45STejun Heo 		 * Gaining baseline access owes an update_idle() so the sched
558*ca3aec45STejun Heo 		 * learns the cpu's idle state. Arm the per-rq gate so the next
559*ca3aec45STejun Heo 		 * idle pick flushes it. Losing access drops any pending notify.
560*ca3aec45STejun Heo 		 */
561*ca3aec45STejun Heo 		if (gained & SCX_CAP_BASE) {
562*ca3aec45STejun Heo 			pcpu->idle_renotify = true;
563*ca3aec45STejun Heo 			rq->scx.flags |= SCX_RQ_SUB_IDLE_RENOTIFY;
564*ca3aec45STejun Heo 		} else if (lost & SCX_CAP_BASE) {
565*ca3aec45STejun Heo 			pcpu->idle_renotify = false;
566*ca3aec45STejun Heo 		}
567b81a6c01STejun Heo 	}
56875a8c820STejun Heo 
56975a8c820STejun Heo 	/*
57075a8c820STejun Heo 	 * Losing a cap can strand already-queued tasks. Schedule a reenq scan
57175a8c820STejun Heo 	 * to move the now-capless ones off the local DSQ. The scan tests
57275a8c820STejun Heo 	 * against the effective caps and thus must come after the ecaps sync.
57375a8c820STejun Heo 	 */
57475a8c820STejun Heo 	if (lost_all & SCX_CAPS_REENQ_ON_LOSS)
57575a8c820STejun Heo 		scx_schedule_reenq_local(rq, SCX_REENQ_CAP_REVOKE);
576b81a6c01STejun Heo }
577b81a6c01STejun Heo 
578b81a6c01STejun Heo /*
579b81a6c01STejun Heo  * A cpu came back. Re-seed each sub-sched's ecaps on the cpu's cid. The sync
580b81a6c01STejun Heo  * recomputes effective caps from the pshard and fires ops.sub_ecaps_updated()
581b81a6c01STejun Heo  * only on a real change since offline.
582b81a6c01STejun Heo  */
583b81a6c01STejun Heo void scx_online_ecaps(struct rq *rq)
584b81a6c01STejun Heo {
585b81a6c01STejun Heo 	s32 cid = __scx_cpu_to_cid(cpu_of(rq));
586b81a6c01STejun Heo 	s32 shard = scx_cid_to_shard[cid];
587b81a6c01STejun Heo 	struct scx_sched *pos;
588b81a6c01STejun Heo 
589b81a6c01STejun Heo 	guard(rq_lock_irqsave)(rq);
590b81a6c01STejun Heo 
591b81a6c01STejun Heo 	scx_for_each_descendant_pre(pos, scx_root) {
592b81a6c01STejun Heo 		struct scx_pshard *ps;
593b81a6c01STejun Heo 
594b81a6c01STejun Heo 		/* root holds every cap and never uses ecaps */
595b81a6c01STejun Heo 		if (pos == scx_root)
596b81a6c01STejun Heo 			continue;
597b81a6c01STejun Heo 
598b81a6c01STejun Heo 		ps = pos->pshard[shard];
599b81a6c01STejun Heo 		guard(raw_spinlock)(&ps->lock);
600b81a6c01STejun Heo 		queue_sync_ecaps(pos, cid);
601b81a6c01STejun Heo 	}
602b81a6c01STejun Heo }
603b81a6c01STejun Heo 
604b81a6c01STejun Heo /*
605b81a6c01STejun Heo  * A cpu is going down. Zero each sub-sched's in-effect ecaps so cap checks
606b81a6c01STejun Heo  * treat the cpu as capless while offline. Pending and late-queued syncs are
607b81a6c01STejun Heo  * discarded at consumption by scx_process_sync_ecaps() while the cpu is
608b81a6c01STejun Heo  * inactive. Leave reported_ecaps. Ownership is unchanged, so the
609b81a6c01STejun Heo  * scx_online_ecaps() reseed reports only a genuine delta. No callback fires
610b81a6c01STejun Heo  * here.
611b81a6c01STejun Heo  */
612b81a6c01STejun Heo void scx_offline_ecaps(struct rq *rq)
613b81a6c01STejun Heo {
614b81a6c01STejun Heo 	s32 cpu = cpu_of(rq);
615b81a6c01STejun Heo 	struct scx_sched *pos;
616b81a6c01STejun Heo 
617b81a6c01STejun Heo 	guard(rq_lock_irqsave)(rq);
618b81a6c01STejun Heo 
619b81a6c01STejun Heo 	scx_for_each_descendant_pre(pos, scx_root) {
620b81a6c01STejun Heo 		/* root holds every cap and never uses ecaps */
621b81a6c01STejun Heo 		if (pos == scx_root)
622b81a6c01STejun Heo 			continue;
623b81a6c01STejun Heo 
624b81a6c01STejun Heo 		WRITE_ONCE(per_cpu_ptr(pos->pcpu, cpu)->ecaps, 0);
62556fdc35bSTejun Heo 	}
62656fdc35bSTejun Heo }
62756fdc35bSTejun Heo 
62856fdc35bSTejun Heo /*
62956fdc35bSTejun Heo  * @pcpu's sched was unhashed before the grace period, so nothing new queues.
630b81a6c01STejun Heo  * Flush its pending sync so the pcpu can be freed. If the cpu is online and
631b81a6c01STejun Heo  * scx is enabled, drain via balance_one(). Otherwise, discard under the rq
632b81a6c01STejun Heo  * lock.
63356fdc35bSTejun Heo  */
63456fdc35bSTejun Heo void scx_discard_ecaps_to_sync(s32 cpu, struct scx_sched_pcpu *pcpu)
63556fdc35bSTejun Heo {
636b81a6c01STejun Heo 	struct rq *rq = cpu_rq(cpu);
63756fdc35bSTejun Heo 
638b81a6c01STejun Heo 	while (true) {
639b81a6c01STejun Heo 		scoped_guard (rq_lock_irqsave, rq) {
640b81a6c01STejun Heo 			/*
641b81a6c01STejun Heo 			 * scx_process_sync_ecaps() takes the node off the list
642b81a6c01STejun Heo 			 * before it is done accessing @pcpu but does all of it
643b81a6c01STejun Heo 			 * under the rq lock. Off-list observed under the rq
644b81a6c01STejun Heo 			 * lock guarantees that the sync is complete.
645b81a6c01STejun Heo 			 */
646b81a6c01STejun Heo 			if (!llist_on_list(&pcpu->ecaps_to_sync_node))
647b81a6c01STejun Heo 				return;
648b81a6c01STejun Heo 			/*
649b81a6c01STejun Heo 			 * Discard only when the cpu is truly down. cpu_active()
650b81a6c01STejun Heo 			 * is already set when scx_online_ecaps() queues an online
651b81a6c01STejun Heo 			 * resync while SCX_RQ_ONLINE is not - so test cpu_active(),
652b81a6c01STejun Heo 			 * or that resync would be dropped.
653b81a6c01STejun Heo 			 */
654b81a6c01STejun Heo 			if (!scx_enabled() || !cpu_active(cpu)) {
655b81a6c01STejun Heo 				discard_queued_syncs(rq);
656b81a6c01STejun Heo 				return;
657b81a6c01STejun Heo 			}
658b81a6c01STejun Heo 		}
659b81a6c01STejun Heo 		resched_cpu(cpu);
660b81a6c01STejun Heo 		msleep(1);
661b81a6c01STejun Heo 	}
66256fdc35bSTejun Heo }
66356fdc35bSTejun Heo 
66456fdc35bSTejun Heo /**
66556fdc35bSTejun Heo  * scx_discard_stale_ecaps_syncs - Discard ecaps syncs from earlier schedulers
66656fdc35bSTejun Heo  *
66756fdc35bSTejun Heo  * To be called during root enable before the scheduler goes live. An earlier
66856fdc35bSTejun Heo  * root's sub-sched may not have gone through its RCU free path yet (e.g. a
66956fdc35bSTejun Heo  * still-open link fd defers it) and can leave queued ecaps syncs behind.
67056fdc35bSTejun Heo  * Processing them would decode the dead sched's pshards with the current cid
67156fdc35bSTejun Heo  * layout. Discard them instead. The backing scx_sched_pcpu's are still
67256fdc35bSTejun Heo  * allocated as the free path drains ecaps_to_sync_node before freeing.
67356fdc35bSTejun Heo  */
67456fdc35bSTejun Heo void scx_discard_stale_ecaps_syncs(void)
67556fdc35bSTejun Heo {
67656fdc35bSTejun Heo 	s32 cpu;
67756fdc35bSTejun Heo 
67856fdc35bSTejun Heo 	for_each_possible_cpu(cpu) {
67956fdc35bSTejun Heo 		struct rq *rq = cpu_rq(cpu);
68056fdc35bSTejun Heo 
68156fdc35bSTejun Heo 		guard(rq_lock_irqsave)(rq);
68256fdc35bSTejun Heo 		discard_queued_syncs(rq);
68356fdc35bSTejun Heo 	}
68456fdc35bSTejun Heo }
68556fdc35bSTejun Heo 
686daf8e166STejun Heo static DECLARE_WAIT_QUEUE_HEAD(scx_unlink_waitq);
687daf8e166STejun Heo 
688daf8e166STejun Heo void drain_descendants(struct scx_sched *sch)
689daf8e166STejun Heo {
690daf8e166STejun Heo 	/*
691daf8e166STejun Heo 	 * Child scheds that finished the critical part of disabling will take
692daf8e166STejun Heo 	 * themselves off @sch->children. Wait for it to drain. As propagation
693daf8e166STejun Heo 	 * is recursive, empty @sch->children means that all proper descendant
694daf8e166STejun Heo 	 * scheds reached unlinking stage.
695daf8e166STejun Heo 	 */
696daf8e166STejun Heo 	wait_event(scx_unlink_waitq, list_empty(&sch->children));
697daf8e166STejun Heo }
698daf8e166STejun Heo 
699daf8e166STejun Heo static void scx_fail_parent(struct scx_sched *sch,
700daf8e166STejun Heo 			    struct task_struct *failed, s32 fail_code)
701daf8e166STejun Heo {
702daf8e166STejun Heo 	struct scx_sched *parent = scx_parent(sch);
703daf8e166STejun Heo 	struct scx_task_iter sti;
704daf8e166STejun Heo 	struct task_struct *p;
705daf8e166STejun Heo 
706daf8e166STejun Heo 	scx_error(parent, "ops.init_task() failed (%d) for %s[%d] while disabling a sub-scheduler",
707daf8e166STejun Heo 		  fail_code, failed->comm, failed->pid);
708daf8e166STejun Heo 
709daf8e166STejun Heo 	/*
710daf8e166STejun Heo 	 * Once $parent is bypassed, it's safe to put SCX_TASK_NONE tasks into
711daf8e166STejun Heo 	 * it. This may cause downstream failures on the BPF side but $parent is
712daf8e166STejun Heo 	 * dying anyway.
713daf8e166STejun Heo 	 */
714daf8e166STejun Heo 	scx_bypass(parent, true);
715daf8e166STejun Heo 
716daf8e166STejun Heo 	scx_task_iter_start(&sti, sch->cgrp);
717daf8e166STejun Heo 	while ((p = scx_task_iter_next_locked(&sti))) {
718daf8e166STejun Heo 		if (scx_task_on_sched(parent, p))
719daf8e166STejun Heo 			continue;
720daf8e166STejun Heo 
721daf8e166STejun Heo 		scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE) {
722daf8e166STejun Heo 			scx_disable_and_exit_task(sch, p);
723daf8e166STejun Heo 			scx_set_task_sched(p, parent);
724daf8e166STejun Heo 		}
725daf8e166STejun Heo 	}
726daf8e166STejun Heo 	scx_task_iter_stop(&sti);
727daf8e166STejun Heo }
728daf8e166STejun Heo 
729daf8e166STejun Heo void scx_sub_disable(struct scx_sched *sch)
730daf8e166STejun Heo {
731daf8e166STejun Heo 	struct scx_sched *parent = scx_parent(sch);
732daf8e166STejun Heo 	struct scx_task_iter sti;
733daf8e166STejun Heo 	struct task_struct *p;
734daf8e166STejun Heo 	int ret;
735daf8e166STejun Heo 
736daf8e166STejun Heo 	/*
737daf8e166STejun Heo 	 * Guarantee forward progress and wait for descendants to be disabled.
738daf8e166STejun Heo 	 * To limit disruptions, $parent is not bypassed. Tasks are fully
739daf8e166STejun Heo 	 * prepped and then inserted back into $parent.
740daf8e166STejun Heo 	 */
741daf8e166STejun Heo 	scx_bypass(sch, true);
742daf8e166STejun Heo 	drain_descendants(sch);
743daf8e166STejun Heo 
744daf8e166STejun Heo 	/*
745daf8e166STejun Heo 	 * Here, every runnable task is guaranteed to make forward progress and
746daf8e166STejun Heo 	 * we can safely use blocking synchronization constructs. Actually
747daf8e166STejun Heo 	 * disable ops.
748daf8e166STejun Heo 	 */
749daf8e166STejun Heo 	mutex_lock(&scx_enable_mutex);
750daf8e166STejun Heo 	percpu_down_write(&scx_fork_rwsem);
751daf8e166STejun Heo 	scx_cgroup_lock();
752daf8e166STejun Heo 
753daf8e166STejun Heo 	set_cgroup_sched(sch_cgroup(sch), parent);
754daf8e166STejun Heo 
755daf8e166STejun Heo 	scx_task_iter_start(&sti, sch->cgrp);
756daf8e166STejun Heo 	while ((p = scx_task_iter_next_locked(&sti))) {
757daf8e166STejun Heo 		struct rq *rq;
758daf8e166STejun Heo 		struct rq_flags rf;
759daf8e166STejun Heo 
760daf8e166STejun Heo 		/* filter out duplicate visits */
761daf8e166STejun Heo 		if (scx_task_on_sched(parent, p))
762daf8e166STejun Heo 			continue;
763daf8e166STejun Heo 
764daf8e166STejun Heo 		/*
765daf8e166STejun Heo 		 * By the time control reaches here, all descendant schedulers
766daf8e166STejun Heo 		 * should already have been disabled.
767daf8e166STejun Heo 		 */
768daf8e166STejun Heo 		WARN_ON_ONCE(!scx_task_on_sched(sch, p));
769daf8e166STejun Heo 
770daf8e166STejun Heo 		/*
771daf8e166STejun Heo 		 * @p is pinned by the iter: css_task_iter_next() takes a
772daf8e166STejun Heo 		 * reference and holds it until the next iter_next() call, so
773daf8e166STejun Heo 		 * @p->usage is guaranteed > 0.
774daf8e166STejun Heo 		 */
775daf8e166STejun Heo 		get_task_struct(p);
776daf8e166STejun Heo 
777daf8e166STejun Heo 		scx_task_iter_unlock(&sti);
778daf8e166STejun Heo 
779daf8e166STejun Heo 		/*
780daf8e166STejun Heo 		 * $p is READY or ENABLED on @sch. Initialize for $parent,
781daf8e166STejun Heo 		 * disable and exit from @sch, and then switch over to $parent.
782daf8e166STejun Heo 		 *
783daf8e166STejun Heo 		 * If a task fails to initialize for $parent, the only available
784daf8e166STejun Heo 		 * action is disabling $parent too. While this allows disabling
785daf8e166STejun Heo 		 * of a child sched to cause the parent scheduler to fail, the
786daf8e166STejun Heo 		 * failure can only originate from ops.init_task() of the
787daf8e166STejun Heo 		 * parent. A child can't directly affect the parent through its
788daf8e166STejun Heo 		 * own failures.
789daf8e166STejun Heo 		 */
790daf8e166STejun Heo 		ret = __scx_init_task(parent, p, false);
791daf8e166STejun Heo 		if (ret) {
792daf8e166STejun Heo 			scx_fail_parent(sch, p, ret);
793daf8e166STejun Heo 			put_task_struct(p);
794daf8e166STejun Heo 			break;
795daf8e166STejun Heo 		}
796daf8e166STejun Heo 
797daf8e166STejun Heo 		rq = task_rq_lock(p, &rf);
798daf8e166STejun Heo 
799daf8e166STejun Heo 		if (scx_get_task_state(p) == SCX_TASK_DEAD) {
800daf8e166STejun Heo 			/*
801daf8e166STejun Heo 			 * sched_ext_dead() raced us between __scx_init_task()
802daf8e166STejun Heo 			 * and this rq lock and ran exit_task() on @sch (the
803daf8e166STejun Heo 			 * sched @p was on at that point), not on $parent.
804daf8e166STejun Heo 			 * $parent's just-completed init is owed an exit_task()
805daf8e166STejun Heo 			 * and we issue it here.
806daf8e166STejun Heo 			 */
807daf8e166STejun Heo 			scx_sub_init_cancel_task(parent, p);
808daf8e166STejun Heo 			task_rq_unlock(rq, p, &rf);
809daf8e166STejun Heo 			put_task_struct(p);
810daf8e166STejun Heo 			continue;
811daf8e166STejun Heo 		}
812daf8e166STejun Heo 
813daf8e166STejun Heo 		scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE) {
814daf8e166STejun Heo 			/*
815daf8e166STejun Heo 			 * $p is initialized for $parent and still attached to
816daf8e166STejun Heo 			 * @sch. Disable and exit for @sch, switch over to
817daf8e166STejun Heo 			 * $parent, override the state to READY to account for
818daf8e166STejun Heo 			 * $p having already been initialized, and then enable.
819daf8e166STejun Heo 			 */
820daf8e166STejun Heo 			scx_disable_and_exit_task(sch, p);
821daf8e166STejun Heo 			scx_set_task_state(p, SCX_TASK_INIT_BEGIN);
822daf8e166STejun Heo 			scx_set_task_state(p, SCX_TASK_INIT);
823daf8e166STejun Heo 			scx_set_task_sched(p, parent);
824daf8e166STejun Heo 			scx_set_task_state(p, SCX_TASK_READY);
825daf8e166STejun Heo 			scx_enable_task(parent, p);
826daf8e166STejun Heo 		}
827daf8e166STejun Heo 
828daf8e166STejun Heo 		task_rq_unlock(rq, p, &rf);
829daf8e166STejun Heo 		put_task_struct(p);
830daf8e166STejun Heo 	}
831daf8e166STejun Heo 	scx_task_iter_stop(&sti);
832daf8e166STejun Heo 
833daf8e166STejun Heo 	scx_disable_dump(sch);
834daf8e166STejun Heo 
835daf8e166STejun Heo 	scx_cgroup_unlock();
836daf8e166STejun Heo 	percpu_up_write(&scx_fork_rwsem);
837daf8e166STejun Heo 
838daf8e166STejun Heo 	/*
839daf8e166STejun Heo 	 * All tasks are moved off of @sch but there may still be on-going
840daf8e166STejun Heo 	 * operations (e.g. ops.select_cpu()). Drain them by flushing RCU. Use
841daf8e166STejun Heo 	 * the expedited version as ancestors may be waiting in bypass mode.
842daf8e166STejun Heo 	 * Also, tell the parent that there is no need to keep running bypass
843daf8e166STejun Heo 	 * DSQs for us.
844daf8e166STejun Heo 	 */
845daf8e166STejun Heo 	synchronize_rcu_expedited();
846daf8e166STejun Heo 	scx_disable_bypass_dsp(sch);
847daf8e166STejun Heo 
848daf8e166STejun Heo 	scx_unlink_sched(sch);
849daf8e166STejun Heo 
850daf8e166STejun Heo 	mutex_unlock(&scx_enable_mutex);
851daf8e166STejun Heo 
852daf8e166STejun Heo 	/*
853daf8e166STejun Heo 	 * @sch is now unlinked from the parent's children list. Notify and call
854daf8e166STejun Heo 	 * ops.sub_detach/exit(). Note that ops.sub_detach/exit() must be called
855daf8e166STejun Heo 	 * after unlinking and releasing all locks. See scx_claim_exit().
856daf8e166STejun Heo 	 */
857daf8e166STejun Heo 	wake_up_all(&scx_unlink_waitq);
858daf8e166STejun Heo 
859daf8e166STejun Heo 	if (parent->ops.sub_detach && sch->sub_attached) {
860daf8e166STejun Heo 		struct scx_sub_detach_args sub_detach_args = {
861daf8e166STejun Heo 			.ops = &sch->ops,
862daf8e166STejun Heo 			.cgroup_path = sch->cgrp_path,
863daf8e166STejun Heo 		};
864daf8e166STejun Heo 		SCX_CALL_OP(parent, sub_detach, NULL,
865daf8e166STejun Heo 			    &sub_detach_args);
866daf8e166STejun Heo 	}
867daf8e166STejun Heo 
868daf8e166STejun Heo 	scx_log_sched_disable(sch);
869daf8e166STejun Heo 
870daf8e166STejun Heo 	if (sch->ops.exit)
871daf8e166STejun Heo 		SCX_CALL_OP(sch, exit, NULL, sch->exit_info);
87281507f14STejun Heo 
87381507f14STejun Heo 	/*
87481507f14STejun Heo 	 * @sch's non-ops programs such as timers and tracers can fire after
87581507f14STejun Heo 	 * ops.exit(). Now that exit is complete, stop scx_prog_sched() from
87681507f14STejun Heo 	 * resolving to @sch and drain in-flight resolvers.
87781507f14STejun Heo 	 */
87881507f14STejun Heo 	WRITE_ONCE(sch->dead, true);
87981507f14STejun Heo 	synchronize_rcu();
88081507f14STejun Heo 
881daf8e166STejun Heo 	if (sch->sub_kset)
882daf8e166STejun Heo 		kobject_del(&sch->sub_kset->kobj);
88380e6adaaSTejun Heo 	/* not added if enable failed before scx_sched_sysfs_add() */
88480e6adaaSTejun Heo 	if (sch->kobj.state_in_sysfs)
885daf8e166STejun Heo 		kobject_del(&sch->kobj);
886daf8e166STejun Heo }
887daf8e166STejun Heo 
888daf8e166STejun Heo /* verify that a scheduler can be attached to @cgrp and return the parent */
889daf8e166STejun Heo static struct scx_sched *find_parent_sched(struct cgroup *cgrp)
890daf8e166STejun Heo {
891daf8e166STejun Heo 	struct scx_sched *parent = cgrp->scx_sched;
892daf8e166STejun Heo 	struct scx_sched *pos;
893daf8e166STejun Heo 
894daf8e166STejun Heo 	lockdep_assert_held(&scx_sched_lock);
895daf8e166STejun Heo 
896daf8e166STejun Heo 	/* can't attach twice to the same cgroup */
897daf8e166STejun Heo 	if (parent->cgrp == cgrp)
898daf8e166STejun Heo 		return ERR_PTR(-EBUSY);
899daf8e166STejun Heo 
900daf8e166STejun Heo 	/* does $parent allow sub-scheds? */
901daf8e166STejun Heo 	if (!parent->ops.sub_attach)
902daf8e166STejun Heo 		return ERR_PTR(-EOPNOTSUPP);
903daf8e166STejun Heo 
904daf8e166STejun Heo 	/* can't insert between $parent and its exiting children */
905daf8e166STejun Heo 	list_for_each_entry(pos, &parent->children, sibling)
906daf8e166STejun Heo 		if (cgroup_is_descendant(pos->cgrp, cgrp))
907daf8e166STejun Heo 			return ERR_PTR(-EBUSY);
908daf8e166STejun Heo 
909daf8e166STejun Heo 	return parent;
910daf8e166STejun Heo }
911daf8e166STejun Heo 
912daf8e166STejun Heo static bool assert_task_ready_or_enabled(struct task_struct *p)
913daf8e166STejun Heo {
914daf8e166STejun Heo 	u32 state = scx_get_task_state(p);
915daf8e166STejun Heo 
916daf8e166STejun Heo 	switch (state) {
917daf8e166STejun Heo 	case SCX_TASK_READY:
918daf8e166STejun Heo 	case SCX_TASK_ENABLED:
919daf8e166STejun Heo 		return true;
920daf8e166STejun Heo 	default:
921daf8e166STejun Heo 		WARN_ONCE(true, "sched_ext: Invalid task state %d for %s[%d] during enabling sub sched",
922daf8e166STejun Heo 			  state, p->comm, p->pid);
923daf8e166STejun Heo 		return false;
924daf8e166STejun Heo 	}
925daf8e166STejun Heo }
926daf8e166STejun Heo 
927daf8e166STejun Heo void scx_sub_enable_workfn(struct kthread_work *work)
928daf8e166STejun Heo {
929daf8e166STejun Heo 	struct scx_enable_cmd *cmd = container_of(work, struct scx_enable_cmd, work);
930daf8e166STejun Heo 	struct sched_ext_ops *ops = cmd->ops;
931daf8e166STejun Heo 	struct cgroup *cgrp;
932daf8e166STejun Heo 	struct scx_sched *parent, *sch;
933daf8e166STejun Heo 	struct scx_task_iter sti;
934daf8e166STejun Heo 	struct task_struct *p;
935daf8e166STejun Heo 	s32 i, ret;
936daf8e166STejun Heo 
937daf8e166STejun Heo 	mutex_lock(&scx_enable_mutex);
938daf8e166STejun Heo 
939daf8e166STejun Heo 	if (!scx_enabled()) {
940daf8e166STejun Heo 		ret = -ENODEV;
941daf8e166STejun Heo 		goto out_unlock;
942daf8e166STejun Heo 	}
943daf8e166STejun Heo 
944daf8e166STejun Heo 	/* See scx_root_enable_workfn() for the @ops->priv check. */
945daf8e166STejun Heo 	if (rcu_access_pointer(ops->priv)) {
946daf8e166STejun Heo 		ret = -EBUSY;
947daf8e166STejun Heo 		goto out_unlock;
948daf8e166STejun Heo 	}
949daf8e166STejun Heo 
950daf8e166STejun Heo 	cgrp = cgroup_get_from_id(ops->sub_cgroup_id);
951daf8e166STejun Heo 	if (IS_ERR(cgrp)) {
952daf8e166STejun Heo 		ret = PTR_ERR(cgrp);
953daf8e166STejun Heo 		goto out_unlock;
954daf8e166STejun Heo 	}
955daf8e166STejun Heo 
956daf8e166STejun Heo 	raw_spin_lock_irq(&scx_sched_lock);
957daf8e166STejun Heo 	parent = find_parent_sched(cgrp);
958daf8e166STejun Heo 	if (IS_ERR(parent)) {
959daf8e166STejun Heo 		raw_spin_unlock_irq(&scx_sched_lock);
960daf8e166STejun Heo 		ret = PTR_ERR(parent);
961daf8e166STejun Heo 		goto out_put_cgrp;
962daf8e166STejun Heo 	}
963daf8e166STejun Heo 	kobject_get(&parent->kobj);
964daf8e166STejun Heo 	raw_spin_unlock_irq(&scx_sched_lock);
965daf8e166STejun Heo 
966daf8e166STejun Heo 	/* scx_alloc_and_add_sched() consumes @cgrp whether it succeeds or not */
967daf8e166STejun Heo 	sch = scx_alloc_and_add_sched(cmd, cgrp, parent);
968daf8e166STejun Heo 	kobject_put(&parent->kobj);
969daf8e166STejun Heo 	if (IS_ERR(sch)) {
970daf8e166STejun Heo 		ret = PTR_ERR(sch);
971daf8e166STejun Heo 		goto out_unlock;
972daf8e166STejun Heo 	}
973daf8e166STejun Heo 
97486094b95STejun Heo 	/*
97586094b95STejun Heo 	 * Validate before scx_link_sched() publishes @sch, so an invalid sub
97686094b95STejun Heo 	 * never becomes visible with an unallocated pshard.
97786094b95STejun Heo 	 */
97886094b95STejun Heo 	ret = scx_validate_ops(sch, ops);
97986094b95STejun Heo 	if (ret)
98086094b95STejun Heo 		goto err_disable;
98186094b95STejun Heo 
98286094b95STejun Heo 	/*
98386094b95STejun Heo 	 * Allocate pshard[] before scx_link_sched() publishes @sch into the
98486094b95STejun Heo 	 * parent's RCU children list. A concurrent revoke walking the tree
98586094b95STejun Heo 	 * would otherwise dereference sch->pshard[si] while it's still NULL.
98686094b95STejun Heo 	 * Unlike the root path, the cid shard layout is stable at this point.
98786094b95STejun Heo 	 *
98886094b95STejun Heo 	 * scx_alloc_pshards() skips allocation when @sch's arena pool isn't
98986094b95STejun Heo 	 * initialized, so scx_arena_pool_init() must run first.
99086094b95STejun Heo 	 */
99186094b95STejun Heo 	ret = scx_arena_pool_init(sch);
99286094b95STejun Heo 	if (ret)
99386094b95STejun Heo 		goto err_disable;
99486094b95STejun Heo 
99586094b95STejun Heo 	ret = scx_alloc_pshards(sch);
99686094b95STejun Heo 	if (ret)
99786094b95STejun Heo 		goto err_disable;
99886094b95STejun Heo 
999daf8e166STejun Heo 	ret = scx_link_sched(sch);
1000daf8e166STejun Heo 	if (ret)
1001daf8e166STejun Heo 		goto err_disable;
1002daf8e166STejun Heo 
100380e6adaaSTejun Heo 	ret = scx_sched_sysfs_add(sch);
100480e6adaaSTejun Heo 	if (ret)
100580e6adaaSTejun Heo 		goto err_disable;
100680e6adaaSTejun Heo 
1007daf8e166STejun Heo 	if (sch->level >= SCX_SUB_MAX_DEPTH) {
1008daf8e166STejun Heo 		scx_error(sch, "max nesting depth %d violated",
1009daf8e166STejun Heo 			  SCX_SUB_MAX_DEPTH);
1010daf8e166STejun Heo 		goto err_disable;
1011daf8e166STejun Heo 	}
1012daf8e166STejun Heo 
1013daf8e166STejun Heo 	if (sch->ops.init) {
1014daf8e166STejun Heo 		ret = SCX_CALL_OP_RET(sch, init, NULL);
1015daf8e166STejun Heo 		if (ret) {
1016daf8e166STejun Heo 			ret = scx_ops_sanitize_err(sch, "init", ret);
1017daf8e166STejun Heo 			scx_error(sch, "ops.init() failed (%d)", ret);
1018daf8e166STejun Heo 			goto err_disable;
1019daf8e166STejun Heo 		}
1020daf8e166STejun Heo 		sch->exit_info->flags |= SCX_EFLAG_INITIALIZED;
1021daf8e166STejun Heo 	}
1022daf8e166STejun Heo 
1023daf8e166STejun Heo 	ret = scx_set_cmask_scratch_alloc(sch);
1024daf8e166STejun Heo 	if (ret)
1025daf8e166STejun Heo 		goto err_disable;
1026daf8e166STejun Heo 
1027daf8e166STejun Heo 	struct scx_sub_attach_args sub_attach_args = {
1028daf8e166STejun Heo 		.ops = &sch->ops,
1029daf8e166STejun Heo 		.cgroup_path = sch->cgrp_path,
1030daf8e166STejun Heo 	};
1031daf8e166STejun Heo 
1032daf8e166STejun Heo 	ret = SCX_CALL_OP_RET(parent, sub_attach, NULL,
1033daf8e166STejun Heo 			      &sub_attach_args);
1034daf8e166STejun Heo 	if (ret) {
1035daf8e166STejun Heo 		ret = scx_ops_sanitize_err(sch, "sub_attach", ret);
1036daf8e166STejun Heo 		scx_error(sch, "parent rejected (%d)", ret);
1037daf8e166STejun Heo 		goto err_disable;
1038daf8e166STejun Heo 	}
1039daf8e166STejun Heo 	sch->sub_attached = true;
1040daf8e166STejun Heo 
1041daf8e166STejun Heo 	scx_bypass(sch, true);
1042daf8e166STejun Heo 
1043daf8e166STejun Heo 	for (i = SCX_OPI_BEGIN; i < SCX_OPI_END; i++)
1044daf8e166STejun Heo 		if (((void (**)(void))ops)[i])
1045daf8e166STejun Heo 			set_bit(i, sch->has_op);
1046daf8e166STejun Heo 
1047daf8e166STejun Heo 	percpu_down_write(&scx_fork_rwsem);
1048daf8e166STejun Heo 	scx_cgroup_lock();
1049daf8e166STejun Heo 
1050daf8e166STejun Heo 	/*
1051daf8e166STejun Heo 	 * Set cgroup->scx_sched's and check CSS_ONLINE. Either we see
1052daf8e166STejun Heo 	 * !CSS_ONLINE or scx_cgroup_lifetime_notify() sees and shoots us down.
1053daf8e166STejun Heo 	 */
1054daf8e166STejun Heo 	set_cgroup_sched(sch_cgroup(sch), sch);
1055daf8e166STejun Heo 	if (!(cgrp->self.flags & CSS_ONLINE)) {
1056daf8e166STejun Heo 		scx_error(sch, "cgroup is not online");
1057daf8e166STejun Heo 		goto err_unlock_and_disable;
1058daf8e166STejun Heo 	}
1059daf8e166STejun Heo 
1060daf8e166STejun Heo 	/*
1061daf8e166STejun Heo 	 * Initialize tasks for the new child $sch without exiting them for
1062daf8e166STejun Heo 	 * $parent so that the tasks can always be reverted back to $parent
1063daf8e166STejun Heo 	 * sched on child init failure.
1064daf8e166STejun Heo 	 */
1065daf8e166STejun Heo 	WARN_ON_ONCE(scx_enabling_sub_sched);
1066daf8e166STejun Heo 	scx_enabling_sub_sched = sch;
1067daf8e166STejun Heo 
1068daf8e166STejun Heo 	scx_task_iter_start(&sti, sch->cgrp);
1069daf8e166STejun Heo 	while ((p = scx_task_iter_next_locked(&sti))) {
1070daf8e166STejun Heo 		struct rq *rq;
1071daf8e166STejun Heo 		struct rq_flags rf;
1072daf8e166STejun Heo 
1073daf8e166STejun Heo 		/*
1074daf8e166STejun Heo 		 * Task iteration may visit the same task twice when racing
1075daf8e166STejun Heo 		 * against exiting. Use %SCX_TASK_SUB_INIT to mark tasks which
1076daf8e166STejun Heo 		 * finished __scx_init_task() and skip if set.
1077daf8e166STejun Heo 		 *
1078daf8e166STejun Heo 		 * A task may exit and get freed between __scx_init_task()
1079daf8e166STejun Heo 		 * completion and scx_enable_task(). In such cases,
1080daf8e166STejun Heo 		 * scx_disable_and_exit_task() must exit the task for both the
1081daf8e166STejun Heo 		 * parent and child scheds.
1082daf8e166STejun Heo 		 */
1083daf8e166STejun Heo 		if (p->scx.flags & SCX_TASK_SUB_INIT)
1084daf8e166STejun Heo 			continue;
1085daf8e166STejun Heo 
1086daf8e166STejun Heo 		/* @p is pinned by the iter; see scx_sub_disable() */
1087daf8e166STejun Heo 		get_task_struct(p);
1088daf8e166STejun Heo 
1089daf8e166STejun Heo 		if (!assert_task_ready_or_enabled(p)) {
1090daf8e166STejun Heo 			ret = -EINVAL;
1091daf8e166STejun Heo 			goto abort;
1092daf8e166STejun Heo 		}
1093daf8e166STejun Heo 
1094daf8e166STejun Heo 		scx_task_iter_unlock(&sti);
1095daf8e166STejun Heo 
1096daf8e166STejun Heo 		/*
1097daf8e166STejun Heo 		 * As $p is still on $parent, it can't be transitioned to INIT.
1098daf8e166STejun Heo 		 * Let's worry about task state later. Use __scx_init_task().
1099daf8e166STejun Heo 		 */
1100daf8e166STejun Heo 		ret = __scx_init_task(sch, p, false);
1101daf8e166STejun Heo 		if (ret)
1102daf8e166STejun Heo 			goto abort;
1103daf8e166STejun Heo 
1104daf8e166STejun Heo 		rq = task_rq_lock(p, &rf);
1105daf8e166STejun Heo 
1106daf8e166STejun Heo 		if (scx_get_task_state(p) == SCX_TASK_DEAD) {
1107daf8e166STejun Heo 			/*
1108daf8e166STejun Heo 			 * sched_ext_dead() raced us between __scx_init_task()
1109daf8e166STejun Heo 			 * and this rq lock and ran exit_task() on $parent (the
1110daf8e166STejun Heo 			 * sched @p was on at that point), not on @sch. @sch's
1111daf8e166STejun Heo 			 * just-completed init is owed an exit_task() and we
1112daf8e166STejun Heo 			 * issue it here.
1113daf8e166STejun Heo 			 */
1114daf8e166STejun Heo 			scx_sub_init_cancel_task(sch, p);
1115daf8e166STejun Heo 			task_rq_unlock(rq, p, &rf);
1116daf8e166STejun Heo 			put_task_struct(p);
1117daf8e166STejun Heo 			continue;
1118daf8e166STejun Heo 		}
1119daf8e166STejun Heo 
1120daf8e166STejun Heo 		p->scx.flags |= SCX_TASK_SUB_INIT;
1121daf8e166STejun Heo 		task_rq_unlock(rq, p, &rf);
1122daf8e166STejun Heo 
1123daf8e166STejun Heo 		put_task_struct(p);
1124daf8e166STejun Heo 	}
1125daf8e166STejun Heo 	scx_task_iter_stop(&sti);
1126daf8e166STejun Heo 
1127daf8e166STejun Heo 	/*
1128daf8e166STejun Heo 	 * All tasks are prepped. Disable/exit tasks for $parent and enable for
1129daf8e166STejun Heo 	 * the new @sch.
1130daf8e166STejun Heo 	 */
1131daf8e166STejun Heo 	scx_task_iter_start(&sti, sch->cgrp);
1132daf8e166STejun Heo 	while ((p = scx_task_iter_next_locked(&sti))) {
1133daf8e166STejun Heo 		/*
1134daf8e166STejun Heo 		 * Use clearing of %SCX_TASK_SUB_INIT to detect and skip
1135daf8e166STejun Heo 		 * duplicate iterations.
1136daf8e166STejun Heo 		 */
1137daf8e166STejun Heo 		if (!(p->scx.flags & SCX_TASK_SUB_INIT))
1138daf8e166STejun Heo 			continue;
1139daf8e166STejun Heo 
1140daf8e166STejun Heo 		scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE) {
1141daf8e166STejun Heo 			/*
1142daf8e166STejun Heo 			 * $p must be either READY or ENABLED. If ENABLED,
1143daf8e166STejun Heo 			 * __scx_disabled_and_exit_task() first disables and
1144daf8e166STejun Heo 			 * makes it READY. However, after exiting $p, it will
1145daf8e166STejun Heo 			 * leave $p as READY.
1146daf8e166STejun Heo 			 */
1147daf8e166STejun Heo 			assert_task_ready_or_enabled(p);
1148daf8e166STejun Heo 			__scx_disable_and_exit_task(parent, p);
1149daf8e166STejun Heo 
1150daf8e166STejun Heo 			/*
1151daf8e166STejun Heo 			 * $p is now only initialized for @sch and READY, which
1152daf8e166STejun Heo 			 * is what we want. Assign it to @sch and enable.
1153daf8e166STejun Heo 			 */
1154daf8e166STejun Heo 			scx_set_task_sched(p, sch);
1155daf8e166STejun Heo 			scx_enable_task(sch, p);
1156daf8e166STejun Heo 
1157daf8e166STejun Heo 			p->scx.flags &= ~SCX_TASK_SUB_INIT;
1158daf8e166STejun Heo 		}
1159daf8e166STejun Heo 	}
1160daf8e166STejun Heo 	scx_task_iter_stop(&sti);
1161daf8e166STejun Heo 
1162daf8e166STejun Heo 	scx_enabling_sub_sched = NULL;
1163daf8e166STejun Heo 
1164daf8e166STejun Heo 	scx_cgroup_unlock();
1165daf8e166STejun Heo 	percpu_up_write(&scx_fork_rwsem);
1166daf8e166STejun Heo 
1167daf8e166STejun Heo 	scx_bypass(sch, false);
1168daf8e166STejun Heo 
11695f2a9a4cSTejun Heo 	/* @sch is enabled; deliver any caps owed since its sub_attach() */
11705f2a9a4cSTejun Heo 	scx_sub_seed_caps(sch);
11715f2a9a4cSTejun Heo 
1172daf8e166STejun Heo 	pr_info("sched_ext: BPF sub-scheduler \"%s\" enabled\n", sch->ops.name);
1173daf8e166STejun Heo 	kobject_uevent(&sch->kobj, KOBJ_ADD);
1174daf8e166STejun Heo 	ret = 0;
1175daf8e166STejun Heo 	goto out_unlock;
1176daf8e166STejun Heo 
1177daf8e166STejun Heo out_put_cgrp:
1178daf8e166STejun Heo 	cgroup_put(cgrp);
1179daf8e166STejun Heo out_unlock:
1180daf8e166STejun Heo 	mutex_unlock(&scx_enable_mutex);
1181daf8e166STejun Heo 	cmd->ret = ret;
1182daf8e166STejun Heo 	return;
1183daf8e166STejun Heo 
1184daf8e166STejun Heo abort:
1185daf8e166STejun Heo 	put_task_struct(p);
1186daf8e166STejun Heo 	scx_task_iter_stop(&sti);
1187daf8e166STejun Heo 
1188daf8e166STejun Heo 	/*
1189daf8e166STejun Heo 	 * Undo __scx_init_task() for tasks we marked. scx_enable_task() never
1190daf8e166STejun Heo 	 * ran for @sch on them, so calling scx_disable_task() here would invoke
1191daf8e166STejun Heo 	 * ops.disable() without a matching ops.enable(). scx_enabling_sub_sched
1192daf8e166STejun Heo 	 * must stay set until SUB_INIT is cleared from every marked task -
1193daf8e166STejun Heo 	 * scx_disable_and_exit_task() reads it when a task exits concurrently.
1194daf8e166STejun Heo 	 */
1195daf8e166STejun Heo 	scx_task_iter_start(&sti, sch->cgrp);
1196daf8e166STejun Heo 	while ((p = scx_task_iter_next_locked(&sti))) {
1197daf8e166STejun Heo 		if (p->scx.flags & SCX_TASK_SUB_INIT) {
1198daf8e166STejun Heo 			scx_sub_init_cancel_task(sch, p);
1199daf8e166STejun Heo 			p->scx.flags &= ~SCX_TASK_SUB_INIT;
1200daf8e166STejun Heo 		}
1201daf8e166STejun Heo 	}
1202daf8e166STejun Heo 	scx_task_iter_stop(&sti);
1203daf8e166STejun Heo 	scx_enabling_sub_sched = NULL;
1204daf8e166STejun Heo err_unlock_and_disable:
1205daf8e166STejun Heo 	/* we'll soon enter disable path, keep bypass on */
1206daf8e166STejun Heo 	scx_cgroup_unlock();
1207daf8e166STejun Heo 	percpu_up_write(&scx_fork_rwsem);
1208daf8e166STejun Heo err_disable:
1209daf8e166STejun Heo 	mutex_unlock(&scx_enable_mutex);
1210ad45691dSTejun Heo 	/*
1211ad45691dSTejun Heo 	 * Some enable failures only return an errno (e.g. -ENOMEM from an
1212ad45691dSTejun Heo 	 * allocation) without calling scx_error(). Record it so
1213ad45691dSTejun Heo 	 * scx_flush_disable_work() runs the disable and ops.exit() fires.
1214ad45691dSTejun Heo 	 */
1215ad45691dSTejun Heo 	scx_error(sch, "scx_sub_enable() failed (%d)", ret);
1216daf8e166STejun Heo 	scx_flush_disable_work(sch);
1217daf8e166STejun Heo 	cmd->ret = 0;
1218daf8e166STejun Heo }
1219daf8e166STejun Heo 
1220daf8e166STejun Heo static s32 scx_cgroup_lifetime_notify(struct notifier_block *nb,
1221daf8e166STejun Heo 				      unsigned long action, void *data)
1222daf8e166STejun Heo {
1223daf8e166STejun Heo 	struct cgroup *cgrp = data;
1224daf8e166STejun Heo 	struct cgroup *parent = cgroup_parent(cgrp);
1225daf8e166STejun Heo 
1226daf8e166STejun Heo 	if (!cgroup_on_dfl(cgrp))
1227daf8e166STejun Heo 		return NOTIFY_OK;
1228daf8e166STejun Heo 
1229daf8e166STejun Heo 	switch (action) {
1230daf8e166STejun Heo 	case CGROUP_LIFETIME_ONLINE:
1231daf8e166STejun Heo 		/* inherit ->scx_sched from $parent */
1232daf8e166STejun Heo 		if (parent)
1233daf8e166STejun Heo 			rcu_assign_pointer(cgrp->scx_sched, parent->scx_sched);
1234daf8e166STejun Heo 		break;
1235daf8e166STejun Heo 	case CGROUP_LIFETIME_OFFLINE:
1236daf8e166STejun Heo 		/* if there is a sched attached, shoot it down */
1237daf8e166STejun Heo 		if (cgrp->scx_sched && cgrp->scx_sched->cgrp == cgrp)
1238daf8e166STejun Heo 			scx_exit(cgrp->scx_sched, SCX_EXIT_UNREG_KERN,
1239daf8e166STejun Heo 				 SCX_ECODE_RSN_CGROUP_OFFLINE,
1240daf8e166STejun Heo 				 "cgroup %llu going offline", cgroup_id(cgrp));
1241daf8e166STejun Heo 		break;
1242daf8e166STejun Heo 	}
1243daf8e166STejun Heo 
1244daf8e166STejun Heo 	return NOTIFY_OK;
1245daf8e166STejun Heo }
1246daf8e166STejun Heo 
1247daf8e166STejun Heo static struct notifier_block scx_cgroup_lifetime_nb = {
1248daf8e166STejun Heo 	.notifier_call = scx_cgroup_lifetime_notify,
1249daf8e166STejun Heo };
1250daf8e166STejun Heo 
1251daf8e166STejun Heo static s32 __init scx_cgroup_lifetime_notifier_init(void)
1252daf8e166STejun Heo {
1253daf8e166STejun Heo 	return blocking_notifier_chain_register(&cgroup_lifetime_notifier,
1254daf8e166STejun Heo 						&scx_cgroup_lifetime_nb);
1255daf8e166STejun Heo }
1256daf8e166STejun Heo core_initcall(scx_cgroup_lifetime_notifier_init);
1257daf8e166STejun Heo 
12585f2a9a4cSTejun Heo static void scx_pstack_recursion(struct bpf_prog *prog, const char *op)
1259daf8e166STejun Heo {
1260daf8e166STejun Heo 	struct scx_sched *sch;
1261daf8e166STejun Heo 
1262daf8e166STejun Heo 	guard(rcu)();
1263daf8e166STejun Heo 	sch = scx_prog_sched(prog->aux);
1264daf8e166STejun Heo 	if (unlikely(!sch))
1265daf8e166STejun Heo 		return;
1266daf8e166STejun Heo 
12675f2a9a4cSTejun Heo 	scx_error(sch, "%s recursion detected", op);
12685f2a9a4cSTejun Heo }
12695f2a9a4cSTejun Heo 
12705f2a9a4cSTejun Heo void scx_pstack_recursion_on_dispatch(struct bpf_prog *prog)
12715f2a9a4cSTejun Heo {
12725f2a9a4cSTejun Heo 	scx_pstack_recursion(prog, "dispatch");
12735f2a9a4cSTejun Heo }
12745f2a9a4cSTejun Heo 
12755f2a9a4cSTejun Heo void scx_pstack_recursion_on_caps_updated(struct bpf_prog *prog)
12765f2a9a4cSTejun Heo {
12775f2a9a4cSTejun Heo 	scx_pstack_recursion(prog, "sub_caps_updated");
1278daf8e166STejun Heo }
1279daf8e166STejun Heo 
1280daf8e166STejun Heo __bpf_kfunc_start_defs();
1281daf8e166STejun Heo 
1282daf8e166STejun Heo /**
1283daf8e166STejun Heo  * scx_bpf_sub_dispatch - Trigger dispatching on a child scheduler
1284daf8e166STejun Heo  * @cgroup_id: cgroup ID of the child scheduler to dispatch
1285daf8e166STejun Heo  * @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
1286daf8e166STejun Heo  *
1287daf8e166STejun Heo  * Allows a parent scheduler to trigger dispatching on one of its direct
1288daf8e166STejun Heo  * child schedulers. The child scheduler runs its dispatch operation to
1289daf8e166STejun Heo  * move tasks from dispatch queues to the local runqueue.
1290daf8e166STejun Heo  *
1291daf8e166STejun Heo  * Returns: true on success, false if cgroup_id is invalid, not a direct
1292daf8e166STejun Heo  * child, or caller lacks dispatch permission.
1293daf8e166STejun Heo  */
1294daf8e166STejun Heo __bpf_kfunc bool scx_bpf_sub_dispatch(u64 cgroup_id, const struct bpf_prog_aux *aux)
1295daf8e166STejun Heo {
1296daf8e166STejun Heo 	struct rq *this_rq = this_rq();
1297daf8e166STejun Heo 	struct scx_sched *parent, *child;
1298daf8e166STejun Heo 
1299daf8e166STejun Heo 	guard(rcu)();
1300daf8e166STejun Heo 	parent = scx_prog_sched(aux);
1301daf8e166STejun Heo 	if (unlikely(!parent))
1302daf8e166STejun Heo 		return false;
1303daf8e166STejun Heo 
1304daf8e166STejun Heo 	child = scx_find_sub_sched(cgroup_id);
1305daf8e166STejun Heo 
1306daf8e166STejun Heo 	if (unlikely(!child))
1307daf8e166STejun Heo 		return false;
1308daf8e166STejun Heo 
1309daf8e166STejun Heo 	if (unlikely(scx_parent(child) != parent)) {
1310daf8e166STejun Heo 		scx_error(parent, "trying to dispatch a distant sub-sched on cgroup %llu",
1311daf8e166STejun Heo 			  cgroup_id);
1312daf8e166STejun Heo 		return false;
1313daf8e166STejun Heo 	}
1314daf8e166STejun Heo 
1315147d1885STejun Heo 	/*
1316147d1885STejun Heo 	 * Skip a child that does not effectively hold the base cap on this cpu:
1317147d1885STejun Heo 	 * its inserts would only be rejected. ecaps are synced at the top of
1318147d1885STejun Heo 	 * balance_one() before dispatch, so this reflects the in-effect state.
1319147d1885STejun Heo 	 */
1320147d1885STejun Heo 	if (scx_missing_caps(child, cpu_of(this_rq), SCX_CAP_BASE))
1321147d1885STejun Heo 		return false;
1322147d1885STejun Heo 
1323daf8e166STejun Heo 	return scx_dispatch_sched(child, this_rq, this_rq->scx.sub_dispatch_prev,
1324daf8e166STejun Heo 				  true);
1325daf8e166STejun Heo }
1326daf8e166STejun Heo 
132786094b95STejun Heo /* Validate common inputs. On success, *parent_out and *child_out are set. */
132886094b95STejun Heo static s32 sub_cap_preamble(u64 cgroup_id, u64 caps, const struct bpf_prog_aux *aux,
132986094b95STejun Heo 			    struct scx_sched **parent_out, struct scx_sched **child_out)
133086094b95STejun Heo {
133186094b95STejun Heo 	struct scx_sched *parent, *child;
133286094b95STejun Heo 
133386094b95STejun Heo 	parent = scx_prog_sched(aux);
133486094b95STejun Heo 	if (unlikely(!parent))
133586094b95STejun Heo 		return -ENODEV;
133686094b95STejun Heo 
133786094b95STejun Heo 	if (!scx_is_cid_type()) {
133886094b95STejun Heo 		scx_error(parent, "sub-cap kfuncs require a cid-form scheduler");
133986094b95STejun Heo 		return -EOPNOTSUPP;
134086094b95STejun Heo 	}
134186094b95STejun Heo 
134286094b95STejun Heo 	child = scx_find_sub_sched(cgroup_id);
134386094b95STejun Heo 	if (unlikely(!child))
134486094b95STejun Heo 		return -ENODEV;
134586094b95STejun Heo 
134686094b95STejun Heo 	if (unlikely(scx_parent(child) != parent)) {
134786094b95STejun Heo 		scx_error(parent, "%s: sub-%llu is not a direct child",
134886094b95STejun Heo 			  parent->cgrp_path, cgroup_id);
134986094b95STejun Heo 		return -EINVAL;
135086094b95STejun Heo 	}
135186094b95STejun Heo 
135286094b95STejun Heo 	if (unlikely(caps & ~__SCX_CAP_ALL)) {
135386094b95STejun Heo 		scx_error(parent, "invalid caps 0x%llx", caps);
135486094b95STejun Heo 		return -EINVAL;
135586094b95STejun Heo 	}
135686094b95STejun Heo 
135786094b95STejun Heo 	*parent_out = parent;
135886094b95STejun Heo 	*child_out = child;
135986094b95STejun Heo 	return 0;
136086094b95STejun Heo }
136186094b95STejun Heo 
136286094b95STejun Heo /**
136386094b95STejun Heo  * scx_bpf_sub_grant - Grant @caps on @cmask__ign's cids to a direct child
136486094b95STejun Heo  * @cgroup_id: cgroup id of the direct child sub-sched
136586094b95STejun Heo  * @caps: bitmask of SCX_CAP_* to grant
136686094b95STejun Heo  * @cmask__ign: cid cmask to grant @caps on (arena pointer)
136786094b95STejun Heo  * @denied_out__ign: optional arena cmask accumulating refused cids
136886094b95STejun Heo  * @aux: implicit BPF argument
136986094b95STejun Heo  *
137086094b95STejun Heo  * A cid in @cmask__ign is granted to the child only if the parent holds every
137186094b95STejun Heo  * requested cap on it. Refused cids are OR'd into @denied_out__ign when
137286094b95STejun Heo  * provided. Refusals outside @denied_out__ign's range are not recorded.
137386094b95STejun Heo  *
137486094b95STejun Heo  * All-or-nothing keeps the caller-visible result binary per cid, so
137586094b95STejun Heo  * @denied_out__ign is one mask to interpret rather than a per-cap matrix.
137686094b95STejun Heo  *
137786094b95STejun Heo  * Return 0 on full success, -EPERM if any cid was refused, or a negative
137886094b95STejun Heo  * errno on other failures.
137986094b95STejun Heo  */
138086094b95STejun Heo __bpf_kfunc s32 scx_bpf_sub_grant(u64 cgroup_id, u64 caps,
138186094b95STejun Heo 				  const struct scx_cmask *cmask__ign,
138286094b95STejun Heo 				  struct scx_cmask *denied_out__ign,
138386094b95STejun Heo 				  const struct bpf_prog_aux *aux)
138486094b95STejun Heo {
138586094b95STejun Heo 	struct scx_cmask_ref ref, denied_ref;
138686094b95STejun Heo 	struct scx_sched *parent, *child;
138786094b95STejun Heo 	bool any_denied = false;
13885f2a9a4cSTejun Heo 	LIST_HEAD(to_deliver);
138986094b95STejun Heo 	s32 si, ret;
139086094b95STejun Heo 
139186094b95STejun Heo 	guard(irqsave)();
139286094b95STejun Heo 
139386094b95STejun Heo 	ret = sub_cap_preamble(cgroup_id, caps, aux, &parent, &child);
139486094b95STejun Heo 	if (ret)
139586094b95STejun Heo 		return ret;
139686094b95STejun Heo 
139786094b95STejun Heo 	ret = scx_cmask_ref_init(parent, cmask__ign, &ref);
139886094b95STejun Heo 	if (ret) {
139986094b95STejun Heo 		scx_error(parent, "invalid cmask (%d)", ret);
140086094b95STejun Heo 		return ret;
140186094b95STejun Heo 	}
140286094b95STejun Heo 
140386094b95STejun Heo 	if (denied_out__ign) {
140486094b95STejun Heo 		ret = scx_cmask_ref_init(parent, denied_out__ign, &denied_ref);
140586094b95STejun Heo 		if (ret) {
140686094b95STejun Heo 			scx_error(parent, "invalid denied_out (%d)", ret);
140786094b95STejun Heo 			return ret;
140886094b95STejun Heo 		}
140986094b95STejun Heo 	}
141086094b95STejun Heo 
141186094b95STejun Heo 	/* apply the grant one shard at a time */
141286094b95STejun Heo 	for (si = ref.shard_first; si < ref.shard_end; si++) {
141386094b95STejun Heo 		SCX_CMASK_DEFINE_SHARD(slice, 0, SCX_CID_SHARD_MAX_CPUS);
141486094b95STejun Heo 		struct scx_pshard *pps = parent->pshard[si];
141586094b95STejun Heo 		struct scx_pshard *cps = child->pshard[si];
14165f2a9a4cSTejun Heo 		u64 granted_caps = 0;
141786094b95STejun Heo 		u32 cap_bit;
141886094b95STejun Heo 
141986094b95STejun Heo 		scx_cmask_ref_shard(&ref, si, slice);
142086094b95STejun Heo 		if (scx_cmask_empty(slice))
142186094b95STejun Heo 			continue;
142286094b95STejun Heo 
142386094b95STejun Heo 		SCX_CMASK_DEFINE_SHARD(granted_cids, slice->base, slice->nr_cids);
14245f2a9a4cSTejun Heo 		SCX_CMASK_DEFINE_SHARD(changed_cids, slice->base, slice->nr_cids);
14255f2a9a4cSTejun Heo 		SCX_CMASK_DEFINE_SHARD(delta, slice->base, slice->nr_cids);
14265f2a9a4cSTejun Heo 
142786094b95STejun Heo 		scx_cmask_copy(granted_cids, slice);
142886094b95STejun Heo 
142986094b95STejun Heo 		scoped_guard (raw_spinlock, &pps->lock) {
143086094b95STejun Heo 			guard(raw_spinlock_nested)(&cps->lock);
143186094b95STejun Heo 
143286094b95STejun Heo 			/*
143386094b95STejun Heo 			 * Narrow granted_cids to cids the parent holds every
143486094b95STejun Heo 			 * requested cap on. All-or-nothing per cid.
143586094b95STejun Heo 			 */
143686094b95STejun Heo 			scx_for_each_cap_bit(cap_bit, caps)
143786094b95STejun Heo 				scx_cmask_and(granted_cids, &pps->caps[cap_bit].cmask);
143886094b95STejun Heo 
14395f2a9a4cSTejun Heo 			/*
14405f2a9a4cSTejun Heo 			 * For each requested cap, fold the newly-set cids into
14415f2a9a4cSTejun Heo 			 * the child and accumulate the delta.
14425f2a9a4cSTejun Heo 			 */
14435f2a9a4cSTejun Heo 			scx_for_each_cap_bit(cap_bit, caps) {
14445f2a9a4cSTejun Heo 				struct scx_cmask *ccm = &cps->caps[cap_bit].cmask;
14455f2a9a4cSTejun Heo 
14465f2a9a4cSTejun Heo 				scx_cmask_copy(delta, granted_cids);
14475f2a9a4cSTejun Heo 				scx_cmask_andnot(delta, ccm);
14485f2a9a4cSTejun Heo 				if (scx_cmask_empty(delta))
14495f2a9a4cSTejun Heo 					continue;
14505f2a9a4cSTejun Heo 
14515f2a9a4cSTejun Heo 				scx_cmask_or(ccm, delta);
14525f2a9a4cSTejun Heo 				scx_cmask_or(changed_cids, delta);
14535f2a9a4cSTejun Heo 				granted_caps |= BIT_U64(cap_bit);
14545f2a9a4cSTejun Heo 			}
14555f2a9a4cSTejun Heo 
145656fdc35bSTejun Heo 			if (granted_caps) {
145756fdc35bSTejun Heo 				s32 cid;
145856fdc35bSTejun Heo 
14595f2a9a4cSTejun Heo 				caps_updated_record(cps, changed_cids, granted_caps,
14605f2a9a4cSTejun Heo 						    &to_deliver);
1461*ca3aec45STejun Heo 				/*
1462*ca3aec45STejun Heo 				 * The sync arms an update_idle() re-notify if
1463*ca3aec45STejun Heo 				 * the cid gains baseline access, so the holder
1464*ca3aec45STejun Heo 				 * learns of an already-idle cid.
1465*ca3aec45STejun Heo 				 */
146656fdc35bSTejun Heo 				scx_cmask_for_each_cid(cid, changed_cids)
146756fdc35bSTejun Heo 					queue_sync_ecaps(child, cid);
146856fdc35bSTejun Heo 			}
146986094b95STejun Heo 		}
147086094b95STejun Heo 
147186094b95STejun Heo 		/* record cids that didn't make it through into @denied_out */
147286094b95STejun Heo 		if (!scx_cmask_subset(slice, granted_cids)) {
147386094b95STejun Heo 			any_denied = true;
147486094b95STejun Heo 			if (denied_out__ign) {
147586094b95STejun Heo 				SCX_CMASK_DEFINE_SHARD(denied, slice->base, slice->nr_cids);
147686094b95STejun Heo 
147786094b95STejun Heo 				scx_cmask_copy(denied, slice);
147886094b95STejun Heo 				scx_cmask_andnot(denied, granted_cids);
147986094b95STejun Heo 				scx_cmask_ref_or(&denied_ref, denied);
148086094b95STejun Heo 			}
148186094b95STejun Heo 		}
148286094b95STejun Heo 	}
14835f2a9a4cSTejun Heo 
14845f2a9a4cSTejun Heo 	caps_updated_deliver(&to_deliver);
14855f2a9a4cSTejun Heo 
148686094b95STejun Heo 	return any_denied ? -EPERM : 0;
148786094b95STejun Heo }
148886094b95STejun Heo 
148986094b95STejun Heo /**
149086094b95STejun Heo  * scx_bpf_sub_revoke - Revoke @caps on @cmask__ign's cids from @child
149186094b95STejun Heo  * @cgroup_id: cgroup id of the direct child sub-sched
149286094b95STejun Heo  * @caps: bitmask of SCX_CAP_* to revoke
149386094b95STejun Heo  * @cmask__ign: cid cmask to revoke @caps on (arena pointer)
149486094b95STejun Heo  * @aux: implicit BPF argument
149586094b95STejun Heo  *
149686094b95STejun Heo  * Clear @caps bits on @cmask__ign from the child named by @cgroup_id and all
149786094b95STejun Heo  * its descendants. The origin parent's pshard lock is held across the subtree
149886094b95STejun Heo  * walk so a concurrent grant from the origin parent observes the revoked
149986094b95STejun Heo  * state.
150086094b95STejun Heo  */
150186094b95STejun Heo __bpf_kfunc void scx_bpf_sub_revoke(u64 cgroup_id, u64 caps,
150286094b95STejun Heo 				    const struct scx_cmask *cmask__ign,
150386094b95STejun Heo 				    const struct bpf_prog_aux *aux)
150486094b95STejun Heo {
150586094b95STejun Heo 	struct scx_cmask_ref ref;
150686094b95STejun Heo 	struct scx_sched *parent, *child, *pos;
15075f2a9a4cSTejun Heo 	LIST_HEAD(to_deliver);
150886094b95STejun Heo 	s32 si, ret;
150986094b95STejun Heo 
151086094b95STejun Heo 	guard(irqsave)();
151186094b95STejun Heo 
151286094b95STejun Heo 	if (sub_cap_preamble(cgroup_id, caps, aux, &parent, &child))
151386094b95STejun Heo 		return;
151486094b95STejun Heo 
151586094b95STejun Heo 	ret = scx_cmask_ref_init(parent, cmask__ign, &ref);
151686094b95STejun Heo 	if (ret) {
151786094b95STejun Heo 		scx_error(parent, "invalid cmask (%d)", ret);
151886094b95STejun Heo 		return;
151986094b95STejun Heo 	}
152086094b95STejun Heo 
152186094b95STejun Heo 	/* per-shard, walk child's subtree and clear @caps */
152286094b95STejun Heo 	for (si = ref.shard_first; si < ref.shard_end; si++) {
152386094b95STejun Heo 		SCX_CMASK_DEFINE_SHARD(slice, 0, SCX_CID_SHARD_MAX_CPUS);
152486094b95STejun Heo 
152586094b95STejun Heo 		scx_cmask_ref_shard(&ref, si, slice);
152686094b95STejun Heo 		if (scx_cmask_empty(slice))
152786094b95STejun Heo 			continue;
152886094b95STejun Heo 
152986094b95STejun Heo 		/*
153086094b95STejun Heo 		 * Pre-order with subtree skip: a descendant that cleared
153186094b95STejun Heo 		 * nothing means no descendant of it can hold @caps on these
153286094b95STejun Heo 		 * cids either.
153386094b95STejun Heo 		 */
153486094b95STejun Heo 		guard(raw_spinlock)(&parent->pshard[si]->lock);
153586094b95STejun Heo 		pos = scx_next_descendant_pre(NULL, child);
153686094b95STejun Heo 		while (pos) {
153786094b95STejun Heo 			struct scx_pshard *ps = pos->pshard[si];
15385f2a9a4cSTejun Heo 			SCX_CMASK_DEFINE_SHARD(changed_cids, slice->base, slice->nr_cids);
15395f2a9a4cSTejun Heo 			SCX_CMASK_DEFINE_SHARD(delta, slice->base, slice->nr_cids);
154086094b95STejun Heo 			u64 revoked_caps = 0;
154186094b95STejun Heo 			u32 cap_bit;
154286094b95STejun Heo 
154386094b95STejun Heo 			scoped_guard (raw_spinlock_nested, &ps->lock) {
15445f2a9a4cSTejun Heo 				/*
15455f2a9a4cSTejun Heo 				 * For each cap, clear lost cids and accumulate
15465f2a9a4cSTejun Heo 				 * the per-cap diff for notification.
15475f2a9a4cSTejun Heo 				 */
154886094b95STejun Heo 				scx_for_each_cap_bit(cap_bit, caps) {
154986094b95STejun Heo 					struct scx_cmask *cm = &ps->caps[cap_bit].cmask;
155086094b95STejun Heo 
15515f2a9a4cSTejun Heo 					scx_cmask_copy(delta, cm);
15525f2a9a4cSTejun Heo 					scx_cmask_and(delta, slice);
15535f2a9a4cSTejun Heo 					if (scx_cmask_empty(delta))
155486094b95STejun Heo 						continue;
15555f2a9a4cSTejun Heo 
15565f2a9a4cSTejun Heo 					scx_cmask_andnot(cm, delta);
15575f2a9a4cSTejun Heo 					scx_cmask_or(changed_cids, delta);
155886094b95STejun Heo 					revoked_caps |= BIT_U64(cap_bit);
155986094b95STejun Heo 				}
15605f2a9a4cSTejun Heo 
156156fdc35bSTejun Heo 				if (revoked_caps) {
156256fdc35bSTejun Heo 					s32 cid;
156356fdc35bSTejun Heo 
15645f2a9a4cSTejun Heo 					caps_updated_record(ps, changed_cids, revoked_caps,
15655f2a9a4cSTejun Heo 							    &to_deliver);
156656fdc35bSTejun Heo 					scx_cmask_for_each_cid(cid, changed_cids)
156756fdc35bSTejun Heo 						queue_sync_ecaps(pos, cid);
156856fdc35bSTejun Heo 				}
156986094b95STejun Heo 			}
157086094b95STejun Heo 
157186094b95STejun Heo 			if (revoked_caps)
157286094b95STejun Heo 				pos = scx_next_descendant_pre(pos, child);
157386094b95STejun Heo 			else
157486094b95STejun Heo 				pos = scx_skip_subtree_pre(pos, child);
157586094b95STejun Heo 		}
157686094b95STejun Heo 	}
15775f2a9a4cSTejun Heo 
15785f2a9a4cSTejun Heo 	caps_updated_deliver(&to_deliver);
157986094b95STejun Heo }
158086094b95STejun Heo 
158186094b95STejun Heo /**
158286094b95STejun Heo  * scx_bpf_sub_caps - Read self's or a direct child's cap cmasks
158386094b95STejun Heo  * @cgroup_id: 0 for self, or a direct child's cgroup id
158486094b95STejun Heo  * @caps: one or more SCX_CAP_* bits
158586094b95STejun Heo  * @out__ign: arena cmask to receive the union of @caps within its range
158686094b95STejun Heo  * @aux: implicit BPF argument
158786094b95STejun Heo  *
158886094b95STejun Heo  * Read the cap cmasks granted on each cid for self (@cgroup_id 0) or a direct
158986094b95STejun Heo  * child - the literal granted set. A sched can read only itself or a direct
159086094b95STejun Heo  * child.
159186094b95STejun Heo  *
159286094b95STejun Heo  * Return 0, -ENODEV if @cgroup_id names no direct child, or -EINVAL on bad
159386094b95STejun Heo  * inputs.
159486094b95STejun Heo  */
159586094b95STejun Heo __bpf_kfunc s32 scx_bpf_sub_caps(u64 cgroup_id, u64 caps, struct scx_cmask *out__ign,
159686094b95STejun Heo 				 const struct bpf_prog_aux *aux)
159786094b95STejun Heo {
159886094b95STejun Heo 	struct scx_cmask_ref ref;
159986094b95STejun Heo 	struct scx_sched *sch, *target;
160086094b95STejun Heo 	struct scx_pshard **pshard;
160186094b95STejun Heo 	s32 si, ret;
160286094b95STejun Heo 
160386094b95STejun Heo 	guard(irqsave)();
160486094b95STejun Heo 
160586094b95STejun Heo 	sch = scx_prog_sched(aux);
160686094b95STejun Heo 	if (unlikely(!sch))
160786094b95STejun Heo 		return -ENODEV;
160886094b95STejun Heo 
160986094b95STejun Heo 	if (!scx_is_cid_type()) {
161086094b95STejun Heo 		scx_error(sch, "sub-cap kfuncs require a cid-form scheduler");
161186094b95STejun Heo 		return -EOPNOTSUPP;
161286094b95STejun Heo 	}
161386094b95STejun Heo 
161486094b95STejun Heo 	if (unlikely(caps & ~__SCX_CAP_ALL)) {
161586094b95STejun Heo 		scx_error(sch, "invalid caps 0x%llx", caps);
161686094b95STejun Heo 		return -EINVAL;
161786094b95STejun Heo 	}
161886094b95STejun Heo 
161986094b95STejun Heo 	/* @cgroup_id 0 reads self, otherwise a direct child */
162086094b95STejun Heo 	if (cgroup_id) {
162186094b95STejun Heo 		target = scx_find_sub_sched(cgroup_id);
162286094b95STejun Heo 		if (unlikely(!target))
162386094b95STejun Heo 			return -ENODEV;
162486094b95STejun Heo 		if (unlikely(scx_parent(target) != sch)) {
162586094b95STejun Heo 			scx_error(sch, "%s: sub-%llu is not a direct child",
162686094b95STejun Heo 				  sch->cgrp_path, cgroup_id);
162786094b95STejun Heo 			return -EINVAL;
162886094b95STejun Heo 		}
162986094b95STejun Heo 	} else {
163086094b95STejun Heo 		target = sch;
163186094b95STejun Heo 	}
163286094b95STejun Heo 
163386094b95STejun Heo 	/*
163486094b95STejun Heo 	 * The target's caps storage may not be set up yet (e.g. a self-read
163586094b95STejun Heo 	 * during ops.init_cids()). Pairs with the publish in
163686094b95STejun Heo 	 * scx_alloc_pshards(): a non-NULL pshard has every element set.
163786094b95STejun Heo 	 */
163886094b95STejun Heo 	pshard = READ_ONCE(target->pshard);
163986094b95STejun Heo 	if (unlikely(!pshard)) {
164086094b95STejun Heo 		scx_error(sch, "scx_bpf_sub_caps() called before caps storage is initialized");
164186094b95STejun Heo 		return -ENODEV;
164286094b95STejun Heo 	}
164386094b95STejun Heo 
164486094b95STejun Heo 	ret = scx_cmask_ref_init(sch, out__ign, &ref);
164586094b95STejun Heo 	if (ret) {
164686094b95STejun Heo 		scx_error(sch, "invalid out (%d)", ret);
164786094b95STejun Heo 		return ret;
164886094b95STejun Heo 	}
164986094b95STejun Heo 
165086094b95STejun Heo 	for (si = ref.shard_first; si < ref.shard_end; si++) {
165186094b95STejun Heo 		const struct scx_cid_shard *shard = &scx_cid_shard_ranges[si];
165286094b95STejun Heo 		SCX_CMASK_DEFINE_SHARD(local_out, shard->base_cid, shard->nr_cids);
165386094b95STejun Heo 		u32 cap_bit;
165486094b95STejun Heo 
165586094b95STejun Heo 		scx_for_each_cap_bit(cap_bit, caps)
165686094b95STejun Heo 			scx_cmask_or(local_out, &pshard[si]->caps[cap_bit].cmask);
165786094b95STejun Heo 		scx_cmask_ref_copy(&ref, local_out);
165886094b95STejun Heo 	}
165986094b95STejun Heo 	return 0;
166086094b95STejun Heo }
166186094b95STejun Heo 
1662daf8e166STejun Heo __bpf_kfunc_end_defs();
1663daf8e166STejun Heo 
1664daf8e166STejun Heo #endif	/* CONFIG_EXT_SUB_SCHED */
1665