1daf8e166STejun Heo // SPDX-License-Identifier: GPL-2.0 2daf8e166STejun Heo /* 3daf8e166STejun Heo * BPF extensible scheduler class: Documentation/scheduler/sched-ext.rst 4daf8e166STejun Heo * 5daf8e166STejun Heo * Sub-scheduler hierarchy support. 6daf8e166STejun Heo * 7daf8e166STejun Heo * A sub-scheduler is an scx_sched attached to a cgroup subtree under another 8daf8e166STejun Heo * scx_sched. This file holds the sub-scheduler implementation: the scheduler 9daf8e166STejun Heo * tree walk, capability delegation, per-shard cap state and its sync, and the 10daf8e166STejun Heo * sub-scheduler enable/disable paths. The core dispatch/enqueue machinery it 11daf8e166STejun Heo * builds on lives in ext.c. 12daf8e166STejun Heo * 13daf8e166STejun Heo * Copyright (c) 2026 Meta Platforms, Inc. and affiliates. 14daf8e166STejun Heo * Copyright (c) 2026 Tejun Heo <tj@kernel.org> 15daf8e166STejun Heo */ 16daf8e166STejun Heo #include <linux/rhashtable.h> 17daf8e166STejun Heo #include "internal.h" 18daf8e166STejun Heo #include "cid.h" 19daf8e166STejun Heo #include "arena.h" 20daf8e166STejun Heo #include "sub.h" 21daf8e166STejun Heo 22daf8e166STejun Heo #ifdef CONFIG_EXT_SUB_SCHED 23daf8e166STejun Heo 24daf8e166STejun Heo /** 25bbda59d8STejun Heo * scx_skip_subtree_pre - Skip @pos's subtree in a pre-order walk 26bbda59d8STejun Heo * @pos: current position 27bbda59d8STejun Heo * @root: walk root 28bbda59d8STejun Heo * 29bbda59d8STejun Heo * In a walk started by scx_next_descendant_pre(), continue past @pos's subtree: 30bbda59d8STejun Heo * return @pos's next sibling, or the closest ancestor's next sibling, or NULL 31bbda59d8STejun Heo * if @pos's subtree is the last under @root. Same locking rules. 32bbda59d8STejun Heo */ 33bbda59d8STejun Heo struct scx_sched *scx_skip_subtree_pre(struct scx_sched *pos, struct scx_sched *root) 34bbda59d8STejun Heo { 35bbda59d8STejun Heo struct scx_sched *next; 36bbda59d8STejun Heo 37bbda59d8STejun Heo lockdep_assert(lockdep_is_held(&scx_enable_mutex) || 38bbda59d8STejun Heo lockdep_is_held(&scx_sched_lock) || 39bbda59d8STejun Heo rcu_read_lock_any_held()); 40bbda59d8STejun Heo 41bbda59d8STejun Heo while (pos != root) { 42bbda59d8STejun Heo next = list_next_or_null_rcu(&scx_parent(pos)->children, &pos->sibling, 43bbda59d8STejun Heo struct scx_sched, sibling); 44bbda59d8STejun Heo if (next) 45bbda59d8STejun Heo return next; 46bbda59d8STejun Heo pos = scx_parent(pos); 47bbda59d8STejun Heo } 48bbda59d8STejun Heo return NULL; 49bbda59d8STejun Heo } 50bbda59d8STejun Heo 51bbda59d8STejun Heo /** 52daf8e166STejun Heo * scx_next_descendant_pre - find the next descendant for pre-order walk 53daf8e166STejun Heo * @pos: the current position (%NULL to initiate traversal) 54daf8e166STejun Heo * @root: sched whose descendants to walk 55daf8e166STejun Heo * 56daf8e166STejun Heo * To be used by scx_for_each_descendant_pre(). Find the next descendant to 57daf8e166STejun Heo * visit for pre-order traversal of @root's descendants. @root is included in 58daf8e166STejun Heo * the iteration and the first node to be visited. 59daf8e166STejun Heo */ 60daf8e166STejun Heo struct scx_sched *scx_next_descendant_pre(struct scx_sched *pos, struct scx_sched *root) 61daf8e166STejun Heo { 62daf8e166STejun Heo struct scx_sched *next; 63daf8e166STejun Heo 64daf8e166STejun Heo lockdep_assert(lockdep_is_held(&scx_enable_mutex) || 6570f8b178STejun Heo lockdep_is_held(&scx_sched_lock) || 6670f8b178STejun Heo rcu_read_lock_any_held()); 67daf8e166STejun Heo 68daf8e166STejun Heo /* if first iteration, visit @root */ 69daf8e166STejun Heo if (!pos) 70daf8e166STejun Heo return root; 71daf8e166STejun Heo 72daf8e166STejun Heo /* visit the first child if exists */ 7370f8b178STejun Heo next = list_first_or_null_rcu(&pos->children, struct scx_sched, sibling); 74daf8e166STejun Heo if (next) 75daf8e166STejun Heo return next; 76daf8e166STejun Heo 77daf8e166STejun Heo /* no child, visit my or the closest ancestor's next sibling */ 78bbda59d8STejun Heo return scx_skip_subtree_pre(pos, root); 79daf8e166STejun Heo } 80daf8e166STejun Heo 81daf8e166STejun Heo static struct scx_sched *scx_find_sub_sched(u64 cgroup_id) 82daf8e166STejun Heo { 83daf8e166STejun Heo return rhashtable_lookup(&scx_sched_hash, &cgroup_id, 84daf8e166STejun Heo scx_sched_hash_params); 85daf8e166STejun Heo } 86daf8e166STejun Heo 87daf8e166STejun Heo void scx_set_task_sched(struct task_struct *p, struct scx_sched *sch) 88daf8e166STejun Heo { 89daf8e166STejun Heo rcu_assign_pointer(p->scx.sched, sch); 90daf8e166STejun Heo } 91daf8e166STejun Heo 92daf8e166STejun Heo struct cgroup *sch_cgroup(struct scx_sched *sch) 93daf8e166STejun Heo { 94daf8e166STejun Heo return sch->cgrp; 95daf8e166STejun Heo } 96daf8e166STejun Heo 97daf8e166STejun Heo /* for each descendant of @cgrp including self, set ->scx_sched to @sch */ 98daf8e166STejun Heo void set_cgroup_sched(struct cgroup *cgrp, struct scx_sched *sch) 99daf8e166STejun Heo { 100daf8e166STejun Heo struct cgroup *pos; 101daf8e166STejun Heo struct cgroup_subsys_state *css; 102daf8e166STejun Heo 103daf8e166STejun Heo cgroup_for_each_live_descendant_pre(pos, css, cgrp) 104daf8e166STejun Heo rcu_assign_pointer(pos->scx_sched, sch); 105daf8e166STejun Heo } 106daf8e166STejun Heo 1078dba3bbdSTejun Heo static void free_pshard(struct scx_pshard *pshard) 1088dba3bbdSTejun Heo { 1095f2a9a4cSTejun Heo struct scx_caps_updated *cu; 1105f2a9a4cSTejun Heo 1115f2a9a4cSTejun Heo if (!pshard) 1125f2a9a4cSTejun Heo return; 1135f2a9a4cSTejun Heo cu = &pshard->caps_updated; 1145f2a9a4cSTejun Heo if (cu->cmask_arena_out) 1155f2a9a4cSTejun Heo scx_arena_free(pshard->sch, cu->cmask_arena_out, 1165f2a9a4cSTejun Heo struct_size_t(struct scx_cmask, bits, 1175f2a9a4cSTejun Heo SCX_CMASK_NR_WORDS(pshard->nr_cids))); 1188dba3bbdSTejun Heo kfree(pshard); 1198dba3bbdSTejun Heo } 1208dba3bbdSTejun Heo 1218dba3bbdSTejun Heo void scx_free_pshards(struct scx_sched *sch) 1228dba3bbdSTejun Heo { 1238dba3bbdSTejun Heo s32 si; 1248dba3bbdSTejun Heo 1258dba3bbdSTejun Heo if (!sch->pshard) 1268dba3bbdSTejun Heo return; 1278dba3bbdSTejun Heo for (si = 0; si < sch->nr_pshards; si++) 1288dba3bbdSTejun Heo free_pshard(sch->pshard[si]); 1298dba3bbdSTejun Heo kfree(sch->pshard); 1308dba3bbdSTejun Heo } 1318dba3bbdSTejun Heo 1328dba3bbdSTejun Heo static struct scx_pshard *alloc_pshard(struct scx_sched *sch, s32 shard_idx, s32 node) 1338dba3bbdSTejun Heo { 13486094b95STejun Heo const struct scx_cid_shard *shard = &scx_cid_shard_ranges[shard_idx]; 1355f2a9a4cSTejun Heo size_t cmask_size = struct_size_t(struct scx_cmask, bits, 1365f2a9a4cSTejun Heo SCX_CMASK_NR_WORDS(shard->nr_cids)); 13786094b95STejun Heo struct scx_pshard *pshard; 1385f2a9a4cSTejun Heo struct scx_caps_updated *cu; 13986094b95STejun Heo s32 i; 14086094b95STejun Heo 14186094b95STejun Heo pshard = kzalloc_node(sizeof(*pshard), GFP_KERNEL, node); 14286094b95STejun Heo if (!pshard) 14386094b95STejun Heo return NULL; 14486094b95STejun Heo 14586094b95STejun Heo raw_spin_lock_init(&pshard->lock); 14686094b95STejun Heo pshard->sch = sch; 1475f2a9a4cSTejun Heo pshard->base = shard->base_cid; 1485f2a9a4cSTejun Heo pshard->nr_cids = shard->nr_cids; 14986094b95STejun Heo 15086094b95STejun Heo for (i = 0; i < __SCX_NR_CAPS; i++) 15186094b95STejun Heo scx_cmask_init(&pshard->caps[i].cmask, shard->base_cid, shard->nr_cids); 15286094b95STejun Heo 1535f2a9a4cSTejun Heo cu = &pshard->caps_updated; 1545f2a9a4cSTejun Heo raw_spin_lock_init(&cu->lock); 1555f2a9a4cSTejun Heo INIT_LIST_HEAD(&cu->node_in_flight); 1565f2a9a4cSTejun Heo __scx_cmask_init(&cu->cmask, shard->base_cid, shard->nr_cids, SCX_CID_SHARD_MAX_CPUS); 1575f2a9a4cSTejun Heo 1585f2a9a4cSTejun Heo cu->cmask_arena_out = scx_arena_alloc(sch, cmask_size); 1595f2a9a4cSTejun Heo if (!cu->cmask_arena_out) { 1605f2a9a4cSTejun Heo free_pshard(pshard); 1615f2a9a4cSTejun Heo return NULL; 1625f2a9a4cSTejun Heo } 1635f2a9a4cSTejun Heo 1645f2a9a4cSTejun Heo scx_cmask_init(cu->cmask_arena_out, shard->base_cid, shard->nr_cids); 1655f2a9a4cSTejun Heo 16686094b95STejun Heo return pshard; 1678dba3bbdSTejun Heo } 1688dba3bbdSTejun Heo 1698dba3bbdSTejun Heo s32 scx_alloc_pshards(struct scx_sched *sch) 1708dba3bbdSTejun Heo { 1718dba3bbdSTejun Heo struct scx_pshard **pshard; 1728dba3bbdSTejun Heo s32 si; 1738dba3bbdSTejun Heo 1748dba3bbdSTejun Heo if (!sch->is_cid_type || !sch->arena_pool) 1758dba3bbdSTejun Heo return 0; 1768dba3bbdSTejun Heo 1778dba3bbdSTejun Heo pshard = kzalloc_objs(pshard[0], scx_nr_cid_shards, GFP_KERNEL); 1788dba3bbdSTejun Heo if (!pshard) 1798dba3bbdSTejun Heo return -ENOMEM; 1808dba3bbdSTejun Heo 1818dba3bbdSTejun Heo for (si = 0; si < scx_nr_cid_shards; si++) { 1828dba3bbdSTejun Heo pshard[si] = alloc_pshard(sch, si, scx_shard_node[si]); 1838dba3bbdSTejun Heo if (!pshard[si]) { 1848dba3bbdSTejun Heo while (--si >= 0) 1858dba3bbdSTejun Heo free_pshard(pshard[si]); 1868dba3bbdSTejun Heo kfree(pshard); 1878dba3bbdSTejun Heo return -ENOMEM; 1888dba3bbdSTejun Heo } 1898dba3bbdSTejun Heo } 1908dba3bbdSTejun Heo 1918dba3bbdSTejun Heo sch->nr_pshards = scx_nr_cid_shards; 1928dba3bbdSTejun Heo /* 1938dba3bbdSTejun Heo * Publish only after every entry is built so a reader observing 1948dba3bbdSTejun Heo * @sch->pshard never sees a partially-filled array. Pair the store 1958dba3bbdSTejun Heo * with a barrier and READ_ONCE() on the read side. 1968dba3bbdSTejun Heo */ 1978dba3bbdSTejun Heo smp_wmb(); 1988dba3bbdSTejun Heo WRITE_ONCE(sch->pshard, pshard); 1998dba3bbdSTejun Heo return 0; 2008dba3bbdSTejun Heo } 2018dba3bbdSTejun Heo 20286094b95STejun Heo /* 20386094b95STejun Heo * Seed the root's caps fully. Root owns all cids on all caps at enable time. 20486094b95STejun Heo * Children acquire caps via scx_bpf_sub_grant(). 20586094b95STejun Heo */ 20686094b95STejun Heo void scx_init_root_caps(struct scx_sched *sch) 20786094b95STejun Heo { 20886094b95STejun Heo s32 si, i; 20986094b95STejun Heo 21086094b95STejun Heo for (si = 0; si < sch->nr_pshards; si++) { 21186094b95STejun Heo struct scx_pshard *ps = sch->pshard[si]; 21286094b95STejun Heo 21386094b95STejun Heo for (i = 0; i < __SCX_NR_CAPS; i++) 21486094b95STejun Heo scx_cmask_fill(&ps->caps[i].cmask); 21586094b95STejun Heo } 21686094b95STejun Heo } 21786094b95STejun Heo 2185f2a9a4cSTejun Heo /* record a caps change, see struct scx_caps_updated */ 2195f2a9a4cSTejun Heo static void caps_updated_record(struct scx_pshard *ps, const struct scx_cmask *cids, u64 caps, 2205f2a9a4cSTejun Heo struct list_head *to_deliver) 2215f2a9a4cSTejun Heo { 2225f2a9a4cSTejun Heo struct scx_caps_updated *cu = &ps->caps_updated; 2235f2a9a4cSTejun Heo 2245f2a9a4cSTejun Heo guard(raw_spinlock)(&cu->lock); 2255f2a9a4cSTejun Heo scx_cmask_or(&cu->cmask, cids); 2265f2a9a4cSTejun Heo cu->caps |= caps; 2275f2a9a4cSTejun Heo if (list_empty(&cu->node_in_flight)) 2285f2a9a4cSTejun Heo list_add_tail(&cu->node_in_flight, to_deliver); 2295f2a9a4cSTejun Heo } 2305f2a9a4cSTejun Heo 2315f2a9a4cSTejun Heo /* deliver queued caps_updated callbacks, see struct scx_caps_updated */ 2325f2a9a4cSTejun Heo static void caps_updated_deliver(struct list_head *to_deliver) 2335f2a9a4cSTejun Heo { 2345f2a9a4cSTejun Heo struct scx_caps_updated *cu, *tmp; 2355f2a9a4cSTejun Heo 2365f2a9a4cSTejun Heo list_for_each_entry_safe(cu, tmp, to_deliver, node_in_flight) { 2375f2a9a4cSTejun Heo struct scx_pshard *ps = container_of(cu, struct scx_pshard, caps_updated); 2385f2a9a4cSTejun Heo struct scx_sched *sch = ps->sch; 2395f2a9a4cSTejun Heo 2405f2a9a4cSTejun Heo while (true) { 2415f2a9a4cSTejun Heo u64 caps = 0; 2425f2a9a4cSTejun Heo 2435f2a9a4cSTejun Heo /* 2445f2a9a4cSTejun Heo * During enable, has_op is set after ops.sub_attach(), 2455f2a9a4cSTejun Heo * so !has_op means the op is absent or the sched isn't 2465f2a9a4cSTejun Heo * live yet - e.g. caps grant from ops.sub_attach(). 2475f2a9a4cSTejun Heo * Either way don't consume - leave for 2485f2a9a4cSTejun Heo * scx_sub_seed_caps() to deliver once live. 2495f2a9a4cSTejun Heo */ 2505f2a9a4cSTejun Heo scoped_guard (raw_spinlock, &cu->lock) { 2515f2a9a4cSTejun Heo if (cu->caps && SCX_HAS_OP(sch, sub_caps_updated) && 2525f2a9a4cSTejun Heo likely(!READ_ONCE(sch->aborting))) { 2535f2a9a4cSTejun Heo struct scx_cmask_ref ref; 2545f2a9a4cSTejun Heo 2555f2a9a4cSTejun Heo caps = cu->caps; 2565f2a9a4cSTejun Heo scx_cmask_ref_init_kern(sch, cu->cmask_arena_out, 2575f2a9a4cSTejun Heo ps->base, ps->nr_cids, &ref); 2585f2a9a4cSTejun Heo scx_cmask_ref_copy(&ref, &cu->cmask); 2595f2a9a4cSTejun Heo scx_cmask_clear(&cu->cmask); 2605f2a9a4cSTejun Heo cu->caps = 0; 2615f2a9a4cSTejun Heo } else { 2625f2a9a4cSTejun Heo list_del_init(&cu->node_in_flight); 2635f2a9a4cSTejun Heo } 2645f2a9a4cSTejun Heo } 2655f2a9a4cSTejun Heo if (!caps) 2665f2a9a4cSTejun Heo break; 2675f2a9a4cSTejun Heo 2685f2a9a4cSTejun Heo /* caps != 0 only when deliverable (has_op, above) */ 2695f2a9a4cSTejun Heo SCX_CALL_OP(sch, sub_caps_updated, NULL, 2705f2a9a4cSTejun Heo scx_kaddr_to_arena(sch, cu->cmask_arena_out), 2715f2a9a4cSTejun Heo caps); 2725f2a9a4cSTejun Heo } 2735f2a9a4cSTejun Heo } 2745f2a9a4cSTejun Heo } 2755f2a9a4cSTejun Heo 2765f2a9a4cSTejun Heo /* 2775f2a9a4cSTejun Heo * Deliver caps owed to @sch that couldn't be delivered earlier (e.g. a grant 2785f2a9a4cSTejun Heo * taken during its sub_attach(), before has_op was set). Called once @sch is 2795f2a9a4cSTejun Heo * enabled. 2805f2a9a4cSTejun Heo */ 2815f2a9a4cSTejun Heo static void scx_sub_seed_caps(struct scx_sched *sch) 2825f2a9a4cSTejun Heo { 2835f2a9a4cSTejun Heo LIST_HEAD(to_deliver); 2845f2a9a4cSTejun Heo s32 si; 2855f2a9a4cSTejun Heo 2865f2a9a4cSTejun Heo guard(irqsave)(); 2875f2a9a4cSTejun Heo 2885f2a9a4cSTejun Heo for (si = 0; si < sch->nr_pshards; si++) { 2895f2a9a4cSTejun Heo struct scx_pshard *ps = sch->pshard[si]; 2905f2a9a4cSTejun Heo struct scx_caps_updated *cu = &ps->caps_updated; 2915f2a9a4cSTejun Heo 2925f2a9a4cSTejun Heo scoped_guard (raw_spinlock, &cu->lock) { 2935f2a9a4cSTejun Heo if (cu->caps && list_empty(&cu->node_in_flight)) 2945f2a9a4cSTejun Heo list_add_tail(&cu->node_in_flight, &to_deliver); 2955f2a9a4cSTejun Heo } 2965f2a9a4cSTejun Heo } 2975f2a9a4cSTejun Heo caps_updated_deliver(&to_deliver); 2985f2a9a4cSTejun Heo } 2995f2a9a4cSTejun Heo 300*56fdc35bSTejun Heo static u64 calc_effective_caps(struct scx_pshard *ps, s32 cid) 301*56fdc35bSTejun Heo { 302*56fdc35bSTejun Heo u64 ecaps = 0; 303*56fdc35bSTejun Heo u32 cap_bit; 304*56fdc35bSTejun Heo 305*56fdc35bSTejun Heo for (cap_bit = 0; cap_bit < __SCX_NR_CAPS; cap_bit++) 306*56fdc35bSTejun Heo if (scx_cmask_test(cid, &ps->caps[cap_bit].cmask)) 307*56fdc35bSTejun Heo ecaps |= BIT_U64(cap_bit) | scx_caps_implied(BIT_U64(cap_bit)); 308*56fdc35bSTejun Heo return ecaps; 309*56fdc35bSTejun Heo } 310*56fdc35bSTejun Heo 311*56fdc35bSTejun Heo /** 312*56fdc35bSTejun Heo * queue_sync_ecaps - Queue ecaps update for a (sch, cid) pair 313*56fdc35bSTejun Heo * @sch: sched to update 314*56fdc35bSTejun Heo * @cid: cid to update 315*56fdc35bSTejun Heo * 316*56fdc35bSTejun Heo * Queue an ecaps update for @sch's @cid and kick the cpu so that it syncs in 317*56fdc35bSTejun Heo * balance_one(). 318*56fdc35bSTejun Heo */ 319*56fdc35bSTejun Heo static void queue_sync_ecaps(struct scx_sched *sch, s32 cid) 320*56fdc35bSTejun Heo { 321*56fdc35bSTejun Heo s32 cpu = __scx_cid_to_cpu(cid); 322*56fdc35bSTejun Heo struct scx_sched_pcpu *pcpu = per_cpu_ptr(sch->pcpu, cpu); 323*56fdc35bSTejun Heo 324*56fdc35bSTejun Heo /* 325*56fdc35bSTejun Heo * Pairs with smp_mb() in scx_process_sync_ecaps(). Either the check 326*56fdc35bSTejun Heo * below sees the node off the list and queues it, or the in-flight sync 327*56fdc35bSTejun Heo * sees the caps[] update made before this call. 328*56fdc35bSTejun Heo */ 329*56fdc35bSTejun Heo smp_mb(); 330*56fdc35bSTejun Heo 331*56fdc35bSTejun Heo /* @cid's pshard->lock excludes concurrent queueing attempts */ 332*56fdc35bSTejun Heo if (llist_on_list(&pcpu->ecaps_to_sync_node)) 333*56fdc35bSTejun Heo return; 334*56fdc35bSTejun Heo if (llist_add(&pcpu->ecaps_to_sync_node, &cpu_rq(cpu)->scx.ecaps_to_sync)) 335*56fdc35bSTejun Heo scx_kick_cpu(scx_root, cpu, 0); 336*56fdc35bSTejun Heo } 337*56fdc35bSTejun Heo 338*56fdc35bSTejun Heo /* discard @rq's queued ecaps syncs */ 339*56fdc35bSTejun Heo static void discard_queued_syncs(struct rq *rq) 340*56fdc35bSTejun Heo { 341*56fdc35bSTejun Heo struct llist_node *pos, *tmp; 342*56fdc35bSTejun Heo 343*56fdc35bSTejun Heo lockdep_assert_rq_held(rq); 344*56fdc35bSTejun Heo 345*56fdc35bSTejun Heo llist_for_each_safe(pos, tmp, llist_del_all(&rq->scx.ecaps_to_sync)) 346*56fdc35bSTejun Heo init_llist_node(pos); 347*56fdc35bSTejun Heo } 348*56fdc35bSTejun Heo 349*56fdc35bSTejun Heo /** 350*56fdc35bSTejun Heo * scx_process_sync_ecaps - Sync this cpu's ecaps to pshard->caps[] 351*56fdc35bSTejun Heo * @rq: the cid's cpu rq 352*56fdc35bSTejun Heo * 353*56fdc35bSTejun Heo * pshard->caps[] is the target configuration. pcpu->ecaps is the effective 354*56fdc35bSTejun Heo * transposed copy owned by the cid's cpu and written only here under @rq's 355*56fdc35bSTejun Heo * lock. 356*56fdc35bSTejun Heo */ 357*56fdc35bSTejun Heo void scx_process_sync_ecaps(struct rq *rq) 358*56fdc35bSTejun Heo { 359*56fdc35bSTejun Heo s32 cid = __scx_cpu_to_cid(cpu_of(rq)); 360*56fdc35bSTejun Heo s32 shard = scx_cid_to_shard[cid]; 361*56fdc35bSTejun Heo struct llist_node *batch, *pos, *tmp; 362*56fdc35bSTejun Heo 363*56fdc35bSTejun Heo lockdep_assert_rq_held(rq); 364*56fdc35bSTejun Heo 365*56fdc35bSTejun Heo if (likely(llist_empty(&rq->scx.ecaps_to_sync))) 366*56fdc35bSTejun Heo return; 367*56fdc35bSTejun Heo 368*56fdc35bSTejun Heo batch = llist_del_all(&rq->scx.ecaps_to_sync); 369*56fdc35bSTejun Heo llist_for_each_safe(pos, tmp, batch) { 370*56fdc35bSTejun Heo struct scx_sched_pcpu *pcpu = 371*56fdc35bSTejun Heo container_of(pos, struct scx_sched_pcpu, ecaps_to_sync_node); 372*56fdc35bSTejun Heo struct scx_pshard *ps = pcpu->sch->pshard[shard]; 373*56fdc35bSTejun Heo 374*56fdc35bSTejun Heo init_llist_node(pos); 375*56fdc35bSTejun Heo 376*56fdc35bSTejun Heo /* pairs with smp_mb() in queue_sync_ecaps(), see there */ 377*56fdc35bSTejun Heo smp_mb(); 378*56fdc35bSTejun Heo 379*56fdc35bSTejun Heo WRITE_ONCE(pcpu->ecaps, calc_effective_caps(ps, cid)); 380*56fdc35bSTejun Heo } 381*56fdc35bSTejun Heo } 382*56fdc35bSTejun Heo 383*56fdc35bSTejun Heo /* 384*56fdc35bSTejun Heo * @pcpu's sched was unhashed before the grace period, so nothing new queues. 385*56fdc35bSTejun Heo * Flush its pending sync so the pcpu can be freed. scx_process_sync_ecaps() 386*56fdc35bSTejun Heo * takes nodes off the list before syncing and acquiring the rq lock waits for 387*56fdc35bSTejun Heo * any in-flight walk. 388*56fdc35bSTejun Heo */ 389*56fdc35bSTejun Heo void scx_discard_ecaps_to_sync(s32 cpu, struct scx_sched_pcpu *pcpu) 390*56fdc35bSTejun Heo { 391*56fdc35bSTejun Heo scoped_guard (rq_lock_irqsave, cpu_rq(cpu)) 392*56fdc35bSTejun Heo scx_process_sync_ecaps(cpu_rq(cpu)); 393*56fdc35bSTejun Heo 394*56fdc35bSTejun Heo WARN_ON_ONCE(llist_on_list(&pcpu->ecaps_to_sync_node)); 395*56fdc35bSTejun Heo } 396*56fdc35bSTejun Heo 397*56fdc35bSTejun Heo /** 398*56fdc35bSTejun Heo * scx_discard_stale_ecaps_syncs - Discard ecaps syncs from earlier schedulers 399*56fdc35bSTejun Heo * 400*56fdc35bSTejun Heo * To be called during root enable before the scheduler goes live. An earlier 401*56fdc35bSTejun Heo * root's sub-sched may not have gone through its RCU free path yet (e.g. a 402*56fdc35bSTejun Heo * still-open link fd defers it) and can leave queued ecaps syncs behind. 403*56fdc35bSTejun Heo * Processing them would decode the dead sched's pshards with the current cid 404*56fdc35bSTejun Heo * layout. Discard them instead. The backing scx_sched_pcpu's are still 405*56fdc35bSTejun Heo * allocated as the free path drains ecaps_to_sync_node before freeing. 406*56fdc35bSTejun Heo */ 407*56fdc35bSTejun Heo void scx_discard_stale_ecaps_syncs(void) 408*56fdc35bSTejun Heo { 409*56fdc35bSTejun Heo s32 cpu; 410*56fdc35bSTejun Heo 411*56fdc35bSTejun Heo for_each_possible_cpu(cpu) { 412*56fdc35bSTejun Heo struct rq *rq = cpu_rq(cpu); 413*56fdc35bSTejun Heo 414*56fdc35bSTejun Heo guard(rq_lock_irqsave)(rq); 415*56fdc35bSTejun Heo discard_queued_syncs(rq); 416*56fdc35bSTejun Heo } 417*56fdc35bSTejun Heo } 418*56fdc35bSTejun Heo 419daf8e166STejun Heo static DECLARE_WAIT_QUEUE_HEAD(scx_unlink_waitq); 420daf8e166STejun Heo 421daf8e166STejun Heo void drain_descendants(struct scx_sched *sch) 422daf8e166STejun Heo { 423daf8e166STejun Heo /* 424daf8e166STejun Heo * Child scheds that finished the critical part of disabling will take 425daf8e166STejun Heo * themselves off @sch->children. Wait for it to drain. As propagation 426daf8e166STejun Heo * is recursive, empty @sch->children means that all proper descendant 427daf8e166STejun Heo * scheds reached unlinking stage. 428daf8e166STejun Heo */ 429daf8e166STejun Heo wait_event(scx_unlink_waitq, list_empty(&sch->children)); 430daf8e166STejun Heo } 431daf8e166STejun Heo 432daf8e166STejun Heo static void scx_fail_parent(struct scx_sched *sch, 433daf8e166STejun Heo struct task_struct *failed, s32 fail_code) 434daf8e166STejun Heo { 435daf8e166STejun Heo struct scx_sched *parent = scx_parent(sch); 436daf8e166STejun Heo struct scx_task_iter sti; 437daf8e166STejun Heo struct task_struct *p; 438daf8e166STejun Heo 439daf8e166STejun Heo scx_error(parent, "ops.init_task() failed (%d) for %s[%d] while disabling a sub-scheduler", 440daf8e166STejun Heo fail_code, failed->comm, failed->pid); 441daf8e166STejun Heo 442daf8e166STejun Heo /* 443daf8e166STejun Heo * Once $parent is bypassed, it's safe to put SCX_TASK_NONE tasks into 444daf8e166STejun Heo * it. This may cause downstream failures on the BPF side but $parent is 445daf8e166STejun Heo * dying anyway. 446daf8e166STejun Heo */ 447daf8e166STejun Heo scx_bypass(parent, true); 448daf8e166STejun Heo 449daf8e166STejun Heo scx_task_iter_start(&sti, sch->cgrp); 450daf8e166STejun Heo while ((p = scx_task_iter_next_locked(&sti))) { 451daf8e166STejun Heo if (scx_task_on_sched(parent, p)) 452daf8e166STejun Heo continue; 453daf8e166STejun Heo 454daf8e166STejun Heo scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE) { 455daf8e166STejun Heo scx_disable_and_exit_task(sch, p); 456daf8e166STejun Heo scx_set_task_sched(p, parent); 457daf8e166STejun Heo } 458daf8e166STejun Heo } 459daf8e166STejun Heo scx_task_iter_stop(&sti); 460daf8e166STejun Heo } 461daf8e166STejun Heo 462daf8e166STejun Heo void scx_sub_disable(struct scx_sched *sch) 463daf8e166STejun Heo { 464daf8e166STejun Heo struct scx_sched *parent = scx_parent(sch); 465daf8e166STejun Heo struct scx_task_iter sti; 466daf8e166STejun Heo struct task_struct *p; 467daf8e166STejun Heo int ret; 468daf8e166STejun Heo 469daf8e166STejun Heo /* 470daf8e166STejun Heo * Guarantee forward progress and wait for descendants to be disabled. 471daf8e166STejun Heo * To limit disruptions, $parent is not bypassed. Tasks are fully 472daf8e166STejun Heo * prepped and then inserted back into $parent. 473daf8e166STejun Heo */ 474daf8e166STejun Heo scx_bypass(sch, true); 475daf8e166STejun Heo drain_descendants(sch); 476daf8e166STejun Heo 477daf8e166STejun Heo /* 478daf8e166STejun Heo * Here, every runnable task is guaranteed to make forward progress and 479daf8e166STejun Heo * we can safely use blocking synchronization constructs. Actually 480daf8e166STejun Heo * disable ops. 481daf8e166STejun Heo */ 482daf8e166STejun Heo mutex_lock(&scx_enable_mutex); 483daf8e166STejun Heo percpu_down_write(&scx_fork_rwsem); 484daf8e166STejun Heo scx_cgroup_lock(); 485daf8e166STejun Heo 486daf8e166STejun Heo set_cgroup_sched(sch_cgroup(sch), parent); 487daf8e166STejun Heo 488daf8e166STejun Heo scx_task_iter_start(&sti, sch->cgrp); 489daf8e166STejun Heo while ((p = scx_task_iter_next_locked(&sti))) { 490daf8e166STejun Heo struct rq *rq; 491daf8e166STejun Heo struct rq_flags rf; 492daf8e166STejun Heo 493daf8e166STejun Heo /* filter out duplicate visits */ 494daf8e166STejun Heo if (scx_task_on_sched(parent, p)) 495daf8e166STejun Heo continue; 496daf8e166STejun Heo 497daf8e166STejun Heo /* 498daf8e166STejun Heo * By the time control reaches here, all descendant schedulers 499daf8e166STejun Heo * should already have been disabled. 500daf8e166STejun Heo */ 501daf8e166STejun Heo WARN_ON_ONCE(!scx_task_on_sched(sch, p)); 502daf8e166STejun Heo 503daf8e166STejun Heo /* 504daf8e166STejun Heo * @p is pinned by the iter: css_task_iter_next() takes a 505daf8e166STejun Heo * reference and holds it until the next iter_next() call, so 506daf8e166STejun Heo * @p->usage is guaranteed > 0. 507daf8e166STejun Heo */ 508daf8e166STejun Heo get_task_struct(p); 509daf8e166STejun Heo 510daf8e166STejun Heo scx_task_iter_unlock(&sti); 511daf8e166STejun Heo 512daf8e166STejun Heo /* 513daf8e166STejun Heo * $p is READY or ENABLED on @sch. Initialize for $parent, 514daf8e166STejun Heo * disable and exit from @sch, and then switch over to $parent. 515daf8e166STejun Heo * 516daf8e166STejun Heo * If a task fails to initialize for $parent, the only available 517daf8e166STejun Heo * action is disabling $parent too. While this allows disabling 518daf8e166STejun Heo * of a child sched to cause the parent scheduler to fail, the 519daf8e166STejun Heo * failure can only originate from ops.init_task() of the 520daf8e166STejun Heo * parent. A child can't directly affect the parent through its 521daf8e166STejun Heo * own failures. 522daf8e166STejun Heo */ 523daf8e166STejun Heo ret = __scx_init_task(parent, p, false); 524daf8e166STejun Heo if (ret) { 525daf8e166STejun Heo scx_fail_parent(sch, p, ret); 526daf8e166STejun Heo put_task_struct(p); 527daf8e166STejun Heo break; 528daf8e166STejun Heo } 529daf8e166STejun Heo 530daf8e166STejun Heo rq = task_rq_lock(p, &rf); 531daf8e166STejun Heo 532daf8e166STejun Heo if (scx_get_task_state(p) == SCX_TASK_DEAD) { 533daf8e166STejun Heo /* 534daf8e166STejun Heo * sched_ext_dead() raced us between __scx_init_task() 535daf8e166STejun Heo * and this rq lock and ran exit_task() on @sch (the 536daf8e166STejun Heo * sched @p was on at that point), not on $parent. 537daf8e166STejun Heo * $parent's just-completed init is owed an exit_task() 538daf8e166STejun Heo * and we issue it here. 539daf8e166STejun Heo */ 540daf8e166STejun Heo scx_sub_init_cancel_task(parent, p); 541daf8e166STejun Heo task_rq_unlock(rq, p, &rf); 542daf8e166STejun Heo put_task_struct(p); 543daf8e166STejun Heo continue; 544daf8e166STejun Heo } 545daf8e166STejun Heo 546daf8e166STejun Heo scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE) { 547daf8e166STejun Heo /* 548daf8e166STejun Heo * $p is initialized for $parent and still attached to 549daf8e166STejun Heo * @sch. Disable and exit for @sch, switch over to 550daf8e166STejun Heo * $parent, override the state to READY to account for 551daf8e166STejun Heo * $p having already been initialized, and then enable. 552daf8e166STejun Heo */ 553daf8e166STejun Heo scx_disable_and_exit_task(sch, p); 554daf8e166STejun Heo scx_set_task_state(p, SCX_TASK_INIT_BEGIN); 555daf8e166STejun Heo scx_set_task_state(p, SCX_TASK_INIT); 556daf8e166STejun Heo scx_set_task_sched(p, parent); 557daf8e166STejun Heo scx_set_task_state(p, SCX_TASK_READY); 558daf8e166STejun Heo scx_enable_task(parent, p); 559daf8e166STejun Heo } 560daf8e166STejun Heo 561daf8e166STejun Heo task_rq_unlock(rq, p, &rf); 562daf8e166STejun Heo put_task_struct(p); 563daf8e166STejun Heo } 564daf8e166STejun Heo scx_task_iter_stop(&sti); 565daf8e166STejun Heo 566daf8e166STejun Heo scx_disable_dump(sch); 567daf8e166STejun Heo 568daf8e166STejun Heo scx_cgroup_unlock(); 569daf8e166STejun Heo percpu_up_write(&scx_fork_rwsem); 570daf8e166STejun Heo 571daf8e166STejun Heo /* 572daf8e166STejun Heo * All tasks are moved off of @sch but there may still be on-going 573daf8e166STejun Heo * operations (e.g. ops.select_cpu()). Drain them by flushing RCU. Use 574daf8e166STejun Heo * the expedited version as ancestors may be waiting in bypass mode. 575daf8e166STejun Heo * Also, tell the parent that there is no need to keep running bypass 576daf8e166STejun Heo * DSQs for us. 577daf8e166STejun Heo */ 578daf8e166STejun Heo synchronize_rcu_expedited(); 579daf8e166STejun Heo scx_disable_bypass_dsp(sch); 580daf8e166STejun Heo 581daf8e166STejun Heo scx_unlink_sched(sch); 582daf8e166STejun Heo 583daf8e166STejun Heo mutex_unlock(&scx_enable_mutex); 584daf8e166STejun Heo 585daf8e166STejun Heo /* 586daf8e166STejun Heo * @sch is now unlinked from the parent's children list. Notify and call 587daf8e166STejun Heo * ops.sub_detach/exit(). Note that ops.sub_detach/exit() must be called 588daf8e166STejun Heo * after unlinking and releasing all locks. See scx_claim_exit(). 589daf8e166STejun Heo */ 590daf8e166STejun Heo wake_up_all(&scx_unlink_waitq); 591daf8e166STejun Heo 592daf8e166STejun Heo if (parent->ops.sub_detach && sch->sub_attached) { 593daf8e166STejun Heo struct scx_sub_detach_args sub_detach_args = { 594daf8e166STejun Heo .ops = &sch->ops, 595daf8e166STejun Heo .cgroup_path = sch->cgrp_path, 596daf8e166STejun Heo }; 597daf8e166STejun Heo SCX_CALL_OP(parent, sub_detach, NULL, 598daf8e166STejun Heo &sub_detach_args); 599daf8e166STejun Heo } 600daf8e166STejun Heo 601daf8e166STejun Heo scx_log_sched_disable(sch); 602daf8e166STejun Heo 603daf8e166STejun Heo if (sch->ops.exit) 604daf8e166STejun Heo SCX_CALL_OP(sch, exit, NULL, sch->exit_info); 60581507f14STejun Heo 60681507f14STejun Heo /* 60781507f14STejun Heo * @sch's non-ops programs such as timers and tracers can fire after 60881507f14STejun Heo * ops.exit(). Now that exit is complete, stop scx_prog_sched() from 60981507f14STejun Heo * resolving to @sch and drain in-flight resolvers. 61081507f14STejun Heo */ 61181507f14STejun Heo WRITE_ONCE(sch->dead, true); 61281507f14STejun Heo synchronize_rcu(); 61381507f14STejun Heo 614daf8e166STejun Heo if (sch->sub_kset) 615daf8e166STejun Heo kobject_del(&sch->sub_kset->kobj); 61680e6adaaSTejun Heo /* not added if enable failed before scx_sched_sysfs_add() */ 61780e6adaaSTejun Heo if (sch->kobj.state_in_sysfs) 618daf8e166STejun Heo kobject_del(&sch->kobj); 619daf8e166STejun Heo } 620daf8e166STejun Heo 621daf8e166STejun Heo /* verify that a scheduler can be attached to @cgrp and return the parent */ 622daf8e166STejun Heo static struct scx_sched *find_parent_sched(struct cgroup *cgrp) 623daf8e166STejun Heo { 624daf8e166STejun Heo struct scx_sched *parent = cgrp->scx_sched; 625daf8e166STejun Heo struct scx_sched *pos; 626daf8e166STejun Heo 627daf8e166STejun Heo lockdep_assert_held(&scx_sched_lock); 628daf8e166STejun Heo 629daf8e166STejun Heo /* can't attach twice to the same cgroup */ 630daf8e166STejun Heo if (parent->cgrp == cgrp) 631daf8e166STejun Heo return ERR_PTR(-EBUSY); 632daf8e166STejun Heo 633daf8e166STejun Heo /* does $parent allow sub-scheds? */ 634daf8e166STejun Heo if (!parent->ops.sub_attach) 635daf8e166STejun Heo return ERR_PTR(-EOPNOTSUPP); 636daf8e166STejun Heo 637daf8e166STejun Heo /* can't insert between $parent and its exiting children */ 638daf8e166STejun Heo list_for_each_entry(pos, &parent->children, sibling) 639daf8e166STejun Heo if (cgroup_is_descendant(pos->cgrp, cgrp)) 640daf8e166STejun Heo return ERR_PTR(-EBUSY); 641daf8e166STejun Heo 642daf8e166STejun Heo return parent; 643daf8e166STejun Heo } 644daf8e166STejun Heo 645daf8e166STejun Heo static bool assert_task_ready_or_enabled(struct task_struct *p) 646daf8e166STejun Heo { 647daf8e166STejun Heo u32 state = scx_get_task_state(p); 648daf8e166STejun Heo 649daf8e166STejun Heo switch (state) { 650daf8e166STejun Heo case SCX_TASK_READY: 651daf8e166STejun Heo case SCX_TASK_ENABLED: 652daf8e166STejun Heo return true; 653daf8e166STejun Heo default: 654daf8e166STejun Heo WARN_ONCE(true, "sched_ext: Invalid task state %d for %s[%d] during enabling sub sched", 655daf8e166STejun Heo state, p->comm, p->pid); 656daf8e166STejun Heo return false; 657daf8e166STejun Heo } 658daf8e166STejun Heo } 659daf8e166STejun Heo 660daf8e166STejun Heo void scx_sub_enable_workfn(struct kthread_work *work) 661daf8e166STejun Heo { 662daf8e166STejun Heo struct scx_enable_cmd *cmd = container_of(work, struct scx_enable_cmd, work); 663daf8e166STejun Heo struct sched_ext_ops *ops = cmd->ops; 664daf8e166STejun Heo struct cgroup *cgrp; 665daf8e166STejun Heo struct scx_sched *parent, *sch; 666daf8e166STejun Heo struct scx_task_iter sti; 667daf8e166STejun Heo struct task_struct *p; 668daf8e166STejun Heo s32 i, ret; 669daf8e166STejun Heo 670daf8e166STejun Heo mutex_lock(&scx_enable_mutex); 671daf8e166STejun Heo 672daf8e166STejun Heo if (!scx_enabled()) { 673daf8e166STejun Heo ret = -ENODEV; 674daf8e166STejun Heo goto out_unlock; 675daf8e166STejun Heo } 676daf8e166STejun Heo 677daf8e166STejun Heo /* See scx_root_enable_workfn() for the @ops->priv check. */ 678daf8e166STejun Heo if (rcu_access_pointer(ops->priv)) { 679daf8e166STejun Heo ret = -EBUSY; 680daf8e166STejun Heo goto out_unlock; 681daf8e166STejun Heo } 682daf8e166STejun Heo 683daf8e166STejun Heo cgrp = cgroup_get_from_id(ops->sub_cgroup_id); 684daf8e166STejun Heo if (IS_ERR(cgrp)) { 685daf8e166STejun Heo ret = PTR_ERR(cgrp); 686daf8e166STejun Heo goto out_unlock; 687daf8e166STejun Heo } 688daf8e166STejun Heo 689daf8e166STejun Heo raw_spin_lock_irq(&scx_sched_lock); 690daf8e166STejun Heo parent = find_parent_sched(cgrp); 691daf8e166STejun Heo if (IS_ERR(parent)) { 692daf8e166STejun Heo raw_spin_unlock_irq(&scx_sched_lock); 693daf8e166STejun Heo ret = PTR_ERR(parent); 694daf8e166STejun Heo goto out_put_cgrp; 695daf8e166STejun Heo } 696daf8e166STejun Heo kobject_get(&parent->kobj); 697daf8e166STejun Heo raw_spin_unlock_irq(&scx_sched_lock); 698daf8e166STejun Heo 699daf8e166STejun Heo /* scx_alloc_and_add_sched() consumes @cgrp whether it succeeds or not */ 700daf8e166STejun Heo sch = scx_alloc_and_add_sched(cmd, cgrp, parent); 701daf8e166STejun Heo kobject_put(&parent->kobj); 702daf8e166STejun Heo if (IS_ERR(sch)) { 703daf8e166STejun Heo ret = PTR_ERR(sch); 704daf8e166STejun Heo goto out_unlock; 705daf8e166STejun Heo } 706daf8e166STejun Heo 70786094b95STejun Heo /* 70886094b95STejun Heo * Validate before scx_link_sched() publishes @sch, so an invalid sub 70986094b95STejun Heo * never becomes visible with an unallocated pshard. 71086094b95STejun Heo */ 71186094b95STejun Heo ret = scx_validate_ops(sch, ops); 71286094b95STejun Heo if (ret) 71386094b95STejun Heo goto err_disable; 71486094b95STejun Heo 71586094b95STejun Heo /* 71686094b95STejun Heo * Allocate pshard[] before scx_link_sched() publishes @sch into the 71786094b95STejun Heo * parent's RCU children list. A concurrent revoke walking the tree 71886094b95STejun Heo * would otherwise dereference sch->pshard[si] while it's still NULL. 71986094b95STejun Heo * Unlike the root path, the cid shard layout is stable at this point. 72086094b95STejun Heo * 72186094b95STejun Heo * scx_alloc_pshards() skips allocation when @sch's arena pool isn't 72286094b95STejun Heo * initialized, so scx_arena_pool_init() must run first. 72386094b95STejun Heo */ 72486094b95STejun Heo ret = scx_arena_pool_init(sch); 72586094b95STejun Heo if (ret) 72686094b95STejun Heo goto err_disable; 72786094b95STejun Heo 72886094b95STejun Heo ret = scx_alloc_pshards(sch); 72986094b95STejun Heo if (ret) 73086094b95STejun Heo goto err_disable; 73186094b95STejun Heo 732daf8e166STejun Heo ret = scx_link_sched(sch); 733daf8e166STejun Heo if (ret) 734daf8e166STejun Heo goto err_disable; 735daf8e166STejun Heo 73680e6adaaSTejun Heo ret = scx_sched_sysfs_add(sch); 73780e6adaaSTejun Heo if (ret) 73880e6adaaSTejun Heo goto err_disable; 73980e6adaaSTejun Heo 740daf8e166STejun Heo if (sch->level >= SCX_SUB_MAX_DEPTH) { 741daf8e166STejun Heo scx_error(sch, "max nesting depth %d violated", 742daf8e166STejun Heo SCX_SUB_MAX_DEPTH); 743daf8e166STejun Heo goto err_disable; 744daf8e166STejun Heo } 745daf8e166STejun Heo 746daf8e166STejun Heo if (sch->ops.init) { 747daf8e166STejun Heo ret = SCX_CALL_OP_RET(sch, init, NULL); 748daf8e166STejun Heo if (ret) { 749daf8e166STejun Heo ret = scx_ops_sanitize_err(sch, "init", ret); 750daf8e166STejun Heo scx_error(sch, "ops.init() failed (%d)", ret); 751daf8e166STejun Heo goto err_disable; 752daf8e166STejun Heo } 753daf8e166STejun Heo sch->exit_info->flags |= SCX_EFLAG_INITIALIZED; 754daf8e166STejun Heo } 755daf8e166STejun Heo 756daf8e166STejun Heo ret = scx_set_cmask_scratch_alloc(sch); 757daf8e166STejun Heo if (ret) 758daf8e166STejun Heo goto err_disable; 759daf8e166STejun Heo 760daf8e166STejun Heo struct scx_sub_attach_args sub_attach_args = { 761daf8e166STejun Heo .ops = &sch->ops, 762daf8e166STejun Heo .cgroup_path = sch->cgrp_path, 763daf8e166STejun Heo }; 764daf8e166STejun Heo 765daf8e166STejun Heo ret = SCX_CALL_OP_RET(parent, sub_attach, NULL, 766daf8e166STejun Heo &sub_attach_args); 767daf8e166STejun Heo if (ret) { 768daf8e166STejun Heo ret = scx_ops_sanitize_err(sch, "sub_attach", ret); 769daf8e166STejun Heo scx_error(sch, "parent rejected (%d)", ret); 770daf8e166STejun Heo goto err_disable; 771daf8e166STejun Heo } 772daf8e166STejun Heo sch->sub_attached = true; 773daf8e166STejun Heo 774daf8e166STejun Heo scx_bypass(sch, true); 775daf8e166STejun Heo 776daf8e166STejun Heo for (i = SCX_OPI_BEGIN; i < SCX_OPI_END; i++) 777daf8e166STejun Heo if (((void (**)(void))ops)[i]) 778daf8e166STejun Heo set_bit(i, sch->has_op); 779daf8e166STejun Heo 780daf8e166STejun Heo percpu_down_write(&scx_fork_rwsem); 781daf8e166STejun Heo scx_cgroup_lock(); 782daf8e166STejun Heo 783daf8e166STejun Heo /* 784daf8e166STejun Heo * Set cgroup->scx_sched's and check CSS_ONLINE. Either we see 785daf8e166STejun Heo * !CSS_ONLINE or scx_cgroup_lifetime_notify() sees and shoots us down. 786daf8e166STejun Heo */ 787daf8e166STejun Heo set_cgroup_sched(sch_cgroup(sch), sch); 788daf8e166STejun Heo if (!(cgrp->self.flags & CSS_ONLINE)) { 789daf8e166STejun Heo scx_error(sch, "cgroup is not online"); 790daf8e166STejun Heo goto err_unlock_and_disable; 791daf8e166STejun Heo } 792daf8e166STejun Heo 793daf8e166STejun Heo /* 794daf8e166STejun Heo * Initialize tasks for the new child $sch without exiting them for 795daf8e166STejun Heo * $parent so that the tasks can always be reverted back to $parent 796daf8e166STejun Heo * sched on child init failure. 797daf8e166STejun Heo */ 798daf8e166STejun Heo WARN_ON_ONCE(scx_enabling_sub_sched); 799daf8e166STejun Heo scx_enabling_sub_sched = sch; 800daf8e166STejun Heo 801daf8e166STejun Heo scx_task_iter_start(&sti, sch->cgrp); 802daf8e166STejun Heo while ((p = scx_task_iter_next_locked(&sti))) { 803daf8e166STejun Heo struct rq *rq; 804daf8e166STejun Heo struct rq_flags rf; 805daf8e166STejun Heo 806daf8e166STejun Heo /* 807daf8e166STejun Heo * Task iteration may visit the same task twice when racing 808daf8e166STejun Heo * against exiting. Use %SCX_TASK_SUB_INIT to mark tasks which 809daf8e166STejun Heo * finished __scx_init_task() and skip if set. 810daf8e166STejun Heo * 811daf8e166STejun Heo * A task may exit and get freed between __scx_init_task() 812daf8e166STejun Heo * completion and scx_enable_task(). In such cases, 813daf8e166STejun Heo * scx_disable_and_exit_task() must exit the task for both the 814daf8e166STejun Heo * parent and child scheds. 815daf8e166STejun Heo */ 816daf8e166STejun Heo if (p->scx.flags & SCX_TASK_SUB_INIT) 817daf8e166STejun Heo continue; 818daf8e166STejun Heo 819daf8e166STejun Heo /* @p is pinned by the iter; see scx_sub_disable() */ 820daf8e166STejun Heo get_task_struct(p); 821daf8e166STejun Heo 822daf8e166STejun Heo if (!assert_task_ready_or_enabled(p)) { 823daf8e166STejun Heo ret = -EINVAL; 824daf8e166STejun Heo goto abort; 825daf8e166STejun Heo } 826daf8e166STejun Heo 827daf8e166STejun Heo scx_task_iter_unlock(&sti); 828daf8e166STejun Heo 829daf8e166STejun Heo /* 830daf8e166STejun Heo * As $p is still on $parent, it can't be transitioned to INIT. 831daf8e166STejun Heo * Let's worry about task state later. Use __scx_init_task(). 832daf8e166STejun Heo */ 833daf8e166STejun Heo ret = __scx_init_task(sch, p, false); 834daf8e166STejun Heo if (ret) 835daf8e166STejun Heo goto abort; 836daf8e166STejun Heo 837daf8e166STejun Heo rq = task_rq_lock(p, &rf); 838daf8e166STejun Heo 839daf8e166STejun Heo if (scx_get_task_state(p) == SCX_TASK_DEAD) { 840daf8e166STejun Heo /* 841daf8e166STejun Heo * sched_ext_dead() raced us between __scx_init_task() 842daf8e166STejun Heo * and this rq lock and ran exit_task() on $parent (the 843daf8e166STejun Heo * sched @p was on at that point), not on @sch. @sch's 844daf8e166STejun Heo * just-completed init is owed an exit_task() and we 845daf8e166STejun Heo * issue it here. 846daf8e166STejun Heo */ 847daf8e166STejun Heo scx_sub_init_cancel_task(sch, p); 848daf8e166STejun Heo task_rq_unlock(rq, p, &rf); 849daf8e166STejun Heo put_task_struct(p); 850daf8e166STejun Heo continue; 851daf8e166STejun Heo } 852daf8e166STejun Heo 853daf8e166STejun Heo p->scx.flags |= SCX_TASK_SUB_INIT; 854daf8e166STejun Heo task_rq_unlock(rq, p, &rf); 855daf8e166STejun Heo 856daf8e166STejun Heo put_task_struct(p); 857daf8e166STejun Heo } 858daf8e166STejun Heo scx_task_iter_stop(&sti); 859daf8e166STejun Heo 860daf8e166STejun Heo /* 861daf8e166STejun Heo * All tasks are prepped. Disable/exit tasks for $parent and enable for 862daf8e166STejun Heo * the new @sch. 863daf8e166STejun Heo */ 864daf8e166STejun Heo scx_task_iter_start(&sti, sch->cgrp); 865daf8e166STejun Heo while ((p = scx_task_iter_next_locked(&sti))) { 866daf8e166STejun Heo /* 867daf8e166STejun Heo * Use clearing of %SCX_TASK_SUB_INIT to detect and skip 868daf8e166STejun Heo * duplicate iterations. 869daf8e166STejun Heo */ 870daf8e166STejun Heo if (!(p->scx.flags & SCX_TASK_SUB_INIT)) 871daf8e166STejun Heo continue; 872daf8e166STejun Heo 873daf8e166STejun Heo scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE) { 874daf8e166STejun Heo /* 875daf8e166STejun Heo * $p must be either READY or ENABLED. If ENABLED, 876daf8e166STejun Heo * __scx_disabled_and_exit_task() first disables and 877daf8e166STejun Heo * makes it READY. However, after exiting $p, it will 878daf8e166STejun Heo * leave $p as READY. 879daf8e166STejun Heo */ 880daf8e166STejun Heo assert_task_ready_or_enabled(p); 881daf8e166STejun Heo __scx_disable_and_exit_task(parent, p); 882daf8e166STejun Heo 883daf8e166STejun Heo /* 884daf8e166STejun Heo * $p is now only initialized for @sch and READY, which 885daf8e166STejun Heo * is what we want. Assign it to @sch and enable. 886daf8e166STejun Heo */ 887daf8e166STejun Heo scx_set_task_sched(p, sch); 888daf8e166STejun Heo scx_enable_task(sch, p); 889daf8e166STejun Heo 890daf8e166STejun Heo p->scx.flags &= ~SCX_TASK_SUB_INIT; 891daf8e166STejun Heo } 892daf8e166STejun Heo } 893daf8e166STejun Heo scx_task_iter_stop(&sti); 894daf8e166STejun Heo 895daf8e166STejun Heo scx_enabling_sub_sched = NULL; 896daf8e166STejun Heo 897daf8e166STejun Heo scx_cgroup_unlock(); 898daf8e166STejun Heo percpu_up_write(&scx_fork_rwsem); 899daf8e166STejun Heo 900daf8e166STejun Heo scx_bypass(sch, false); 901daf8e166STejun Heo 9025f2a9a4cSTejun Heo /* @sch is enabled; deliver any caps owed since its sub_attach() */ 9035f2a9a4cSTejun Heo scx_sub_seed_caps(sch); 9045f2a9a4cSTejun Heo 905daf8e166STejun Heo pr_info("sched_ext: BPF sub-scheduler \"%s\" enabled\n", sch->ops.name); 906daf8e166STejun Heo kobject_uevent(&sch->kobj, KOBJ_ADD); 907daf8e166STejun Heo ret = 0; 908daf8e166STejun Heo goto out_unlock; 909daf8e166STejun Heo 910daf8e166STejun Heo out_put_cgrp: 911daf8e166STejun Heo cgroup_put(cgrp); 912daf8e166STejun Heo out_unlock: 913daf8e166STejun Heo mutex_unlock(&scx_enable_mutex); 914daf8e166STejun Heo cmd->ret = ret; 915daf8e166STejun Heo return; 916daf8e166STejun Heo 917daf8e166STejun Heo abort: 918daf8e166STejun Heo put_task_struct(p); 919daf8e166STejun Heo scx_task_iter_stop(&sti); 920daf8e166STejun Heo 921daf8e166STejun Heo /* 922daf8e166STejun Heo * Undo __scx_init_task() for tasks we marked. scx_enable_task() never 923daf8e166STejun Heo * ran for @sch on them, so calling scx_disable_task() here would invoke 924daf8e166STejun Heo * ops.disable() without a matching ops.enable(). scx_enabling_sub_sched 925daf8e166STejun Heo * must stay set until SUB_INIT is cleared from every marked task - 926daf8e166STejun Heo * scx_disable_and_exit_task() reads it when a task exits concurrently. 927daf8e166STejun Heo */ 928daf8e166STejun Heo scx_task_iter_start(&sti, sch->cgrp); 929daf8e166STejun Heo while ((p = scx_task_iter_next_locked(&sti))) { 930daf8e166STejun Heo if (p->scx.flags & SCX_TASK_SUB_INIT) { 931daf8e166STejun Heo scx_sub_init_cancel_task(sch, p); 932daf8e166STejun Heo p->scx.flags &= ~SCX_TASK_SUB_INIT; 933daf8e166STejun Heo } 934daf8e166STejun Heo } 935daf8e166STejun Heo scx_task_iter_stop(&sti); 936daf8e166STejun Heo scx_enabling_sub_sched = NULL; 937daf8e166STejun Heo err_unlock_and_disable: 938daf8e166STejun Heo /* we'll soon enter disable path, keep bypass on */ 939daf8e166STejun Heo scx_cgroup_unlock(); 940daf8e166STejun Heo percpu_up_write(&scx_fork_rwsem); 941daf8e166STejun Heo err_disable: 942daf8e166STejun Heo mutex_unlock(&scx_enable_mutex); 943ad45691dSTejun Heo /* 944ad45691dSTejun Heo * Some enable failures only return an errno (e.g. -ENOMEM from an 945ad45691dSTejun Heo * allocation) without calling scx_error(). Record it so 946ad45691dSTejun Heo * scx_flush_disable_work() runs the disable and ops.exit() fires. 947ad45691dSTejun Heo */ 948ad45691dSTejun Heo scx_error(sch, "scx_sub_enable() failed (%d)", ret); 949daf8e166STejun Heo scx_flush_disable_work(sch); 950daf8e166STejun Heo cmd->ret = 0; 951daf8e166STejun Heo } 952daf8e166STejun Heo 953daf8e166STejun Heo static s32 scx_cgroup_lifetime_notify(struct notifier_block *nb, 954daf8e166STejun Heo unsigned long action, void *data) 955daf8e166STejun Heo { 956daf8e166STejun Heo struct cgroup *cgrp = data; 957daf8e166STejun Heo struct cgroup *parent = cgroup_parent(cgrp); 958daf8e166STejun Heo 959daf8e166STejun Heo if (!cgroup_on_dfl(cgrp)) 960daf8e166STejun Heo return NOTIFY_OK; 961daf8e166STejun Heo 962daf8e166STejun Heo switch (action) { 963daf8e166STejun Heo case CGROUP_LIFETIME_ONLINE: 964daf8e166STejun Heo /* inherit ->scx_sched from $parent */ 965daf8e166STejun Heo if (parent) 966daf8e166STejun Heo rcu_assign_pointer(cgrp->scx_sched, parent->scx_sched); 967daf8e166STejun Heo break; 968daf8e166STejun Heo case CGROUP_LIFETIME_OFFLINE: 969daf8e166STejun Heo /* if there is a sched attached, shoot it down */ 970daf8e166STejun Heo if (cgrp->scx_sched && cgrp->scx_sched->cgrp == cgrp) 971daf8e166STejun Heo scx_exit(cgrp->scx_sched, SCX_EXIT_UNREG_KERN, 972daf8e166STejun Heo SCX_ECODE_RSN_CGROUP_OFFLINE, 973daf8e166STejun Heo "cgroup %llu going offline", cgroup_id(cgrp)); 974daf8e166STejun Heo break; 975daf8e166STejun Heo } 976daf8e166STejun Heo 977daf8e166STejun Heo return NOTIFY_OK; 978daf8e166STejun Heo } 979daf8e166STejun Heo 980daf8e166STejun Heo static struct notifier_block scx_cgroup_lifetime_nb = { 981daf8e166STejun Heo .notifier_call = scx_cgroup_lifetime_notify, 982daf8e166STejun Heo }; 983daf8e166STejun Heo 984daf8e166STejun Heo static s32 __init scx_cgroup_lifetime_notifier_init(void) 985daf8e166STejun Heo { 986daf8e166STejun Heo return blocking_notifier_chain_register(&cgroup_lifetime_notifier, 987daf8e166STejun Heo &scx_cgroup_lifetime_nb); 988daf8e166STejun Heo } 989daf8e166STejun Heo core_initcall(scx_cgroup_lifetime_notifier_init); 990daf8e166STejun Heo 9915f2a9a4cSTejun Heo static void scx_pstack_recursion(struct bpf_prog *prog, const char *op) 992daf8e166STejun Heo { 993daf8e166STejun Heo struct scx_sched *sch; 994daf8e166STejun Heo 995daf8e166STejun Heo guard(rcu)(); 996daf8e166STejun Heo sch = scx_prog_sched(prog->aux); 997daf8e166STejun Heo if (unlikely(!sch)) 998daf8e166STejun Heo return; 999daf8e166STejun Heo 10005f2a9a4cSTejun Heo scx_error(sch, "%s recursion detected", op); 10015f2a9a4cSTejun Heo } 10025f2a9a4cSTejun Heo 10035f2a9a4cSTejun Heo void scx_pstack_recursion_on_dispatch(struct bpf_prog *prog) 10045f2a9a4cSTejun Heo { 10055f2a9a4cSTejun Heo scx_pstack_recursion(prog, "dispatch"); 10065f2a9a4cSTejun Heo } 10075f2a9a4cSTejun Heo 10085f2a9a4cSTejun Heo void scx_pstack_recursion_on_caps_updated(struct bpf_prog *prog) 10095f2a9a4cSTejun Heo { 10105f2a9a4cSTejun Heo scx_pstack_recursion(prog, "sub_caps_updated"); 1011daf8e166STejun Heo } 1012daf8e166STejun Heo 1013daf8e166STejun Heo __bpf_kfunc_start_defs(); 1014daf8e166STejun Heo 1015daf8e166STejun Heo /** 1016daf8e166STejun Heo * scx_bpf_sub_dispatch - Trigger dispatching on a child scheduler 1017daf8e166STejun Heo * @cgroup_id: cgroup ID of the child scheduler to dispatch 1018daf8e166STejun Heo * @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs 1019daf8e166STejun Heo * 1020daf8e166STejun Heo * Allows a parent scheduler to trigger dispatching on one of its direct 1021daf8e166STejun Heo * child schedulers. The child scheduler runs its dispatch operation to 1022daf8e166STejun Heo * move tasks from dispatch queues to the local runqueue. 1023daf8e166STejun Heo * 1024daf8e166STejun Heo * Returns: true on success, false if cgroup_id is invalid, not a direct 1025daf8e166STejun Heo * child, or caller lacks dispatch permission. 1026daf8e166STejun Heo */ 1027daf8e166STejun Heo __bpf_kfunc bool scx_bpf_sub_dispatch(u64 cgroup_id, const struct bpf_prog_aux *aux) 1028daf8e166STejun Heo { 1029daf8e166STejun Heo struct rq *this_rq = this_rq(); 1030daf8e166STejun Heo struct scx_sched *parent, *child; 1031daf8e166STejun Heo 1032daf8e166STejun Heo guard(rcu)(); 1033daf8e166STejun Heo parent = scx_prog_sched(aux); 1034daf8e166STejun Heo if (unlikely(!parent)) 1035daf8e166STejun Heo return false; 1036daf8e166STejun Heo 1037daf8e166STejun Heo child = scx_find_sub_sched(cgroup_id); 1038daf8e166STejun Heo 1039daf8e166STejun Heo if (unlikely(!child)) 1040daf8e166STejun Heo return false; 1041daf8e166STejun Heo 1042daf8e166STejun Heo if (unlikely(scx_parent(child) != parent)) { 1043daf8e166STejun Heo scx_error(parent, "trying to dispatch a distant sub-sched on cgroup %llu", 1044daf8e166STejun Heo cgroup_id); 1045daf8e166STejun Heo return false; 1046daf8e166STejun Heo } 1047daf8e166STejun Heo 1048daf8e166STejun Heo return scx_dispatch_sched(child, this_rq, this_rq->scx.sub_dispatch_prev, 1049daf8e166STejun Heo true); 1050daf8e166STejun Heo } 1051daf8e166STejun Heo 105286094b95STejun Heo /* Validate common inputs. On success, *parent_out and *child_out are set. */ 105386094b95STejun Heo static s32 sub_cap_preamble(u64 cgroup_id, u64 caps, const struct bpf_prog_aux *aux, 105486094b95STejun Heo struct scx_sched **parent_out, struct scx_sched **child_out) 105586094b95STejun Heo { 105686094b95STejun Heo struct scx_sched *parent, *child; 105786094b95STejun Heo 105886094b95STejun Heo parent = scx_prog_sched(aux); 105986094b95STejun Heo if (unlikely(!parent)) 106086094b95STejun Heo return -ENODEV; 106186094b95STejun Heo 106286094b95STejun Heo if (!scx_is_cid_type()) { 106386094b95STejun Heo scx_error(parent, "sub-cap kfuncs require a cid-form scheduler"); 106486094b95STejun Heo return -EOPNOTSUPP; 106586094b95STejun Heo } 106686094b95STejun Heo 106786094b95STejun Heo child = scx_find_sub_sched(cgroup_id); 106886094b95STejun Heo if (unlikely(!child)) 106986094b95STejun Heo return -ENODEV; 107086094b95STejun Heo 107186094b95STejun Heo if (unlikely(scx_parent(child) != parent)) { 107286094b95STejun Heo scx_error(parent, "%s: sub-%llu is not a direct child", 107386094b95STejun Heo parent->cgrp_path, cgroup_id); 107486094b95STejun Heo return -EINVAL; 107586094b95STejun Heo } 107686094b95STejun Heo 107786094b95STejun Heo if (unlikely(caps & ~__SCX_CAP_ALL)) { 107886094b95STejun Heo scx_error(parent, "invalid caps 0x%llx", caps); 107986094b95STejun Heo return -EINVAL; 108086094b95STejun Heo } 108186094b95STejun Heo 108286094b95STejun Heo *parent_out = parent; 108386094b95STejun Heo *child_out = child; 108486094b95STejun Heo return 0; 108586094b95STejun Heo } 108686094b95STejun Heo 108786094b95STejun Heo /** 108886094b95STejun Heo * scx_bpf_sub_grant - Grant @caps on @cmask__ign's cids to a direct child 108986094b95STejun Heo * @cgroup_id: cgroup id of the direct child sub-sched 109086094b95STejun Heo * @caps: bitmask of SCX_CAP_* to grant 109186094b95STejun Heo * @cmask__ign: cid cmask to grant @caps on (arena pointer) 109286094b95STejun Heo * @denied_out__ign: optional arena cmask accumulating refused cids 109386094b95STejun Heo * @aux: implicit BPF argument 109486094b95STejun Heo * 109586094b95STejun Heo * A cid in @cmask__ign is granted to the child only if the parent holds every 109686094b95STejun Heo * requested cap on it. Refused cids are OR'd into @denied_out__ign when 109786094b95STejun Heo * provided. Refusals outside @denied_out__ign's range are not recorded. 109886094b95STejun Heo * 109986094b95STejun Heo * All-or-nothing keeps the caller-visible result binary per cid, so 110086094b95STejun Heo * @denied_out__ign is one mask to interpret rather than a per-cap matrix. 110186094b95STejun Heo * 110286094b95STejun Heo * Return 0 on full success, -EPERM if any cid was refused, or a negative 110386094b95STejun Heo * errno on other failures. 110486094b95STejun Heo */ 110586094b95STejun Heo __bpf_kfunc s32 scx_bpf_sub_grant(u64 cgroup_id, u64 caps, 110686094b95STejun Heo const struct scx_cmask *cmask__ign, 110786094b95STejun Heo struct scx_cmask *denied_out__ign, 110886094b95STejun Heo const struct bpf_prog_aux *aux) 110986094b95STejun Heo { 111086094b95STejun Heo struct scx_cmask_ref ref, denied_ref; 111186094b95STejun Heo struct scx_sched *parent, *child; 111286094b95STejun Heo bool any_denied = false; 11135f2a9a4cSTejun Heo LIST_HEAD(to_deliver); 111486094b95STejun Heo s32 si, ret; 111586094b95STejun Heo 111686094b95STejun Heo guard(irqsave)(); 111786094b95STejun Heo 111886094b95STejun Heo ret = sub_cap_preamble(cgroup_id, caps, aux, &parent, &child); 111986094b95STejun Heo if (ret) 112086094b95STejun Heo return ret; 112186094b95STejun Heo 112286094b95STejun Heo ret = scx_cmask_ref_init(parent, cmask__ign, &ref); 112386094b95STejun Heo if (ret) { 112486094b95STejun Heo scx_error(parent, "invalid cmask (%d)", ret); 112586094b95STejun Heo return ret; 112686094b95STejun Heo } 112786094b95STejun Heo 112886094b95STejun Heo if (denied_out__ign) { 112986094b95STejun Heo ret = scx_cmask_ref_init(parent, denied_out__ign, &denied_ref); 113086094b95STejun Heo if (ret) { 113186094b95STejun Heo scx_error(parent, "invalid denied_out (%d)", ret); 113286094b95STejun Heo return ret; 113386094b95STejun Heo } 113486094b95STejun Heo } 113586094b95STejun Heo 113686094b95STejun Heo /* apply the grant one shard at a time */ 113786094b95STejun Heo for (si = ref.shard_first; si < ref.shard_end; si++) { 113886094b95STejun Heo SCX_CMASK_DEFINE_SHARD(slice, 0, SCX_CID_SHARD_MAX_CPUS); 113986094b95STejun Heo struct scx_pshard *pps = parent->pshard[si]; 114086094b95STejun Heo struct scx_pshard *cps = child->pshard[si]; 11415f2a9a4cSTejun Heo u64 granted_caps = 0; 114286094b95STejun Heo u32 cap_bit; 114386094b95STejun Heo 114486094b95STejun Heo scx_cmask_ref_shard(&ref, si, slice); 114586094b95STejun Heo if (scx_cmask_empty(slice)) 114686094b95STejun Heo continue; 114786094b95STejun Heo 114886094b95STejun Heo SCX_CMASK_DEFINE_SHARD(granted_cids, slice->base, slice->nr_cids); 11495f2a9a4cSTejun Heo SCX_CMASK_DEFINE_SHARD(changed_cids, slice->base, slice->nr_cids); 11505f2a9a4cSTejun Heo SCX_CMASK_DEFINE_SHARD(delta, slice->base, slice->nr_cids); 11515f2a9a4cSTejun Heo 115286094b95STejun Heo scx_cmask_copy(granted_cids, slice); 115386094b95STejun Heo 115486094b95STejun Heo scoped_guard (raw_spinlock, &pps->lock) { 115586094b95STejun Heo guard(raw_spinlock_nested)(&cps->lock); 115686094b95STejun Heo 115786094b95STejun Heo /* 115886094b95STejun Heo * Narrow granted_cids to cids the parent holds every 115986094b95STejun Heo * requested cap on. All-or-nothing per cid. 116086094b95STejun Heo */ 116186094b95STejun Heo scx_for_each_cap_bit(cap_bit, caps) 116286094b95STejun Heo scx_cmask_and(granted_cids, &pps->caps[cap_bit].cmask); 116386094b95STejun Heo 11645f2a9a4cSTejun Heo /* 11655f2a9a4cSTejun Heo * For each requested cap, fold the newly-set cids into 11665f2a9a4cSTejun Heo * the child and accumulate the delta. 11675f2a9a4cSTejun Heo */ 11685f2a9a4cSTejun Heo scx_for_each_cap_bit(cap_bit, caps) { 11695f2a9a4cSTejun Heo struct scx_cmask *ccm = &cps->caps[cap_bit].cmask; 11705f2a9a4cSTejun Heo 11715f2a9a4cSTejun Heo scx_cmask_copy(delta, granted_cids); 11725f2a9a4cSTejun Heo scx_cmask_andnot(delta, ccm); 11735f2a9a4cSTejun Heo if (scx_cmask_empty(delta)) 11745f2a9a4cSTejun Heo continue; 11755f2a9a4cSTejun Heo 11765f2a9a4cSTejun Heo scx_cmask_or(ccm, delta); 11775f2a9a4cSTejun Heo scx_cmask_or(changed_cids, delta); 11785f2a9a4cSTejun Heo granted_caps |= BIT_U64(cap_bit); 11795f2a9a4cSTejun Heo } 11805f2a9a4cSTejun Heo 1181*56fdc35bSTejun Heo if (granted_caps) { 1182*56fdc35bSTejun Heo s32 cid; 1183*56fdc35bSTejun Heo 11845f2a9a4cSTejun Heo caps_updated_record(cps, changed_cids, granted_caps, 11855f2a9a4cSTejun Heo &to_deliver); 1186*56fdc35bSTejun Heo scx_cmask_for_each_cid(cid, changed_cids) 1187*56fdc35bSTejun Heo queue_sync_ecaps(child, cid); 1188*56fdc35bSTejun Heo } 118986094b95STejun Heo } 119086094b95STejun Heo 119186094b95STejun Heo /* record cids that didn't make it through into @denied_out */ 119286094b95STejun Heo if (!scx_cmask_subset(slice, granted_cids)) { 119386094b95STejun Heo any_denied = true; 119486094b95STejun Heo if (denied_out__ign) { 119586094b95STejun Heo SCX_CMASK_DEFINE_SHARD(denied, slice->base, slice->nr_cids); 119686094b95STejun Heo 119786094b95STejun Heo scx_cmask_copy(denied, slice); 119886094b95STejun Heo scx_cmask_andnot(denied, granted_cids); 119986094b95STejun Heo scx_cmask_ref_or(&denied_ref, denied); 120086094b95STejun Heo } 120186094b95STejun Heo } 120286094b95STejun Heo } 12035f2a9a4cSTejun Heo 12045f2a9a4cSTejun Heo caps_updated_deliver(&to_deliver); 12055f2a9a4cSTejun Heo 120686094b95STejun Heo return any_denied ? -EPERM : 0; 120786094b95STejun Heo } 120886094b95STejun Heo 120986094b95STejun Heo /** 121086094b95STejun Heo * scx_bpf_sub_revoke - Revoke @caps on @cmask__ign's cids from @child 121186094b95STejun Heo * @cgroup_id: cgroup id of the direct child sub-sched 121286094b95STejun Heo * @caps: bitmask of SCX_CAP_* to revoke 121386094b95STejun Heo * @cmask__ign: cid cmask to revoke @caps on (arena pointer) 121486094b95STejun Heo * @aux: implicit BPF argument 121586094b95STejun Heo * 121686094b95STejun Heo * Clear @caps bits on @cmask__ign from the child named by @cgroup_id and all 121786094b95STejun Heo * its descendants. The origin parent's pshard lock is held across the subtree 121886094b95STejun Heo * walk so a concurrent grant from the origin parent observes the revoked 121986094b95STejun Heo * state. 122086094b95STejun Heo */ 122186094b95STejun Heo __bpf_kfunc void scx_bpf_sub_revoke(u64 cgroup_id, u64 caps, 122286094b95STejun Heo const struct scx_cmask *cmask__ign, 122386094b95STejun Heo const struct bpf_prog_aux *aux) 122486094b95STejun Heo { 122586094b95STejun Heo struct scx_cmask_ref ref; 122686094b95STejun Heo struct scx_sched *parent, *child, *pos; 12275f2a9a4cSTejun Heo LIST_HEAD(to_deliver); 122886094b95STejun Heo s32 si, ret; 122986094b95STejun Heo 123086094b95STejun Heo guard(irqsave)(); 123186094b95STejun Heo 123286094b95STejun Heo if (sub_cap_preamble(cgroup_id, caps, aux, &parent, &child)) 123386094b95STejun Heo return; 123486094b95STejun Heo 123586094b95STejun Heo ret = scx_cmask_ref_init(parent, cmask__ign, &ref); 123686094b95STejun Heo if (ret) { 123786094b95STejun Heo scx_error(parent, "invalid cmask (%d)", ret); 123886094b95STejun Heo return; 123986094b95STejun Heo } 124086094b95STejun Heo 124186094b95STejun Heo /* per-shard, walk child's subtree and clear @caps */ 124286094b95STejun Heo for (si = ref.shard_first; si < ref.shard_end; si++) { 124386094b95STejun Heo SCX_CMASK_DEFINE_SHARD(slice, 0, SCX_CID_SHARD_MAX_CPUS); 124486094b95STejun Heo 124586094b95STejun Heo scx_cmask_ref_shard(&ref, si, slice); 124686094b95STejun Heo if (scx_cmask_empty(slice)) 124786094b95STejun Heo continue; 124886094b95STejun Heo 124986094b95STejun Heo /* 125086094b95STejun Heo * Pre-order with subtree skip: a descendant that cleared 125186094b95STejun Heo * nothing means no descendant of it can hold @caps on these 125286094b95STejun Heo * cids either. 125386094b95STejun Heo */ 125486094b95STejun Heo guard(raw_spinlock)(&parent->pshard[si]->lock); 125586094b95STejun Heo pos = scx_next_descendant_pre(NULL, child); 125686094b95STejun Heo while (pos) { 125786094b95STejun Heo struct scx_pshard *ps = pos->pshard[si]; 12585f2a9a4cSTejun Heo SCX_CMASK_DEFINE_SHARD(changed_cids, slice->base, slice->nr_cids); 12595f2a9a4cSTejun Heo SCX_CMASK_DEFINE_SHARD(delta, slice->base, slice->nr_cids); 126086094b95STejun Heo u64 revoked_caps = 0; 126186094b95STejun Heo u32 cap_bit; 126286094b95STejun Heo 126386094b95STejun Heo scoped_guard (raw_spinlock_nested, &ps->lock) { 12645f2a9a4cSTejun Heo /* 12655f2a9a4cSTejun Heo * For each cap, clear lost cids and accumulate 12665f2a9a4cSTejun Heo * the per-cap diff for notification. 12675f2a9a4cSTejun Heo */ 126886094b95STejun Heo scx_for_each_cap_bit(cap_bit, caps) { 126986094b95STejun Heo struct scx_cmask *cm = &ps->caps[cap_bit].cmask; 127086094b95STejun Heo 12715f2a9a4cSTejun Heo scx_cmask_copy(delta, cm); 12725f2a9a4cSTejun Heo scx_cmask_and(delta, slice); 12735f2a9a4cSTejun Heo if (scx_cmask_empty(delta)) 127486094b95STejun Heo continue; 12755f2a9a4cSTejun Heo 12765f2a9a4cSTejun Heo scx_cmask_andnot(cm, delta); 12775f2a9a4cSTejun Heo scx_cmask_or(changed_cids, delta); 127886094b95STejun Heo revoked_caps |= BIT_U64(cap_bit); 127986094b95STejun Heo } 12805f2a9a4cSTejun Heo 1281*56fdc35bSTejun Heo if (revoked_caps) { 1282*56fdc35bSTejun Heo s32 cid; 1283*56fdc35bSTejun Heo 12845f2a9a4cSTejun Heo caps_updated_record(ps, changed_cids, revoked_caps, 12855f2a9a4cSTejun Heo &to_deliver); 1286*56fdc35bSTejun Heo scx_cmask_for_each_cid(cid, changed_cids) 1287*56fdc35bSTejun Heo queue_sync_ecaps(pos, cid); 1288*56fdc35bSTejun Heo } 128986094b95STejun Heo } 129086094b95STejun Heo 129186094b95STejun Heo if (revoked_caps) 129286094b95STejun Heo pos = scx_next_descendant_pre(pos, child); 129386094b95STejun Heo else 129486094b95STejun Heo pos = scx_skip_subtree_pre(pos, child); 129586094b95STejun Heo } 129686094b95STejun Heo } 12975f2a9a4cSTejun Heo 12985f2a9a4cSTejun Heo caps_updated_deliver(&to_deliver); 129986094b95STejun Heo } 130086094b95STejun Heo 130186094b95STejun Heo /** 130286094b95STejun Heo * scx_bpf_sub_caps - Read self's or a direct child's cap cmasks 130386094b95STejun Heo * @cgroup_id: 0 for self, or a direct child's cgroup id 130486094b95STejun Heo * @caps: one or more SCX_CAP_* bits 130586094b95STejun Heo * @out__ign: arena cmask to receive the union of @caps within its range 130686094b95STejun Heo * @aux: implicit BPF argument 130786094b95STejun Heo * 130886094b95STejun Heo * Read the cap cmasks granted on each cid for self (@cgroup_id 0) or a direct 130986094b95STejun Heo * child - the literal granted set. A sched can read only itself or a direct 131086094b95STejun Heo * child. 131186094b95STejun Heo * 131286094b95STejun Heo * Return 0, -ENODEV if @cgroup_id names no direct child, or -EINVAL on bad 131386094b95STejun Heo * inputs. 131486094b95STejun Heo */ 131586094b95STejun Heo __bpf_kfunc s32 scx_bpf_sub_caps(u64 cgroup_id, u64 caps, struct scx_cmask *out__ign, 131686094b95STejun Heo const struct bpf_prog_aux *aux) 131786094b95STejun Heo { 131886094b95STejun Heo struct scx_cmask_ref ref; 131986094b95STejun Heo struct scx_sched *sch, *target; 132086094b95STejun Heo struct scx_pshard **pshard; 132186094b95STejun Heo s32 si, ret; 132286094b95STejun Heo 132386094b95STejun Heo guard(irqsave)(); 132486094b95STejun Heo 132586094b95STejun Heo sch = scx_prog_sched(aux); 132686094b95STejun Heo if (unlikely(!sch)) 132786094b95STejun Heo return -ENODEV; 132886094b95STejun Heo 132986094b95STejun Heo if (!scx_is_cid_type()) { 133086094b95STejun Heo scx_error(sch, "sub-cap kfuncs require a cid-form scheduler"); 133186094b95STejun Heo return -EOPNOTSUPP; 133286094b95STejun Heo } 133386094b95STejun Heo 133486094b95STejun Heo if (unlikely(caps & ~__SCX_CAP_ALL)) { 133586094b95STejun Heo scx_error(sch, "invalid caps 0x%llx", caps); 133686094b95STejun Heo return -EINVAL; 133786094b95STejun Heo } 133886094b95STejun Heo 133986094b95STejun Heo /* @cgroup_id 0 reads self, otherwise a direct child */ 134086094b95STejun Heo if (cgroup_id) { 134186094b95STejun Heo target = scx_find_sub_sched(cgroup_id); 134286094b95STejun Heo if (unlikely(!target)) 134386094b95STejun Heo return -ENODEV; 134486094b95STejun Heo if (unlikely(scx_parent(target) != sch)) { 134586094b95STejun Heo scx_error(sch, "%s: sub-%llu is not a direct child", 134686094b95STejun Heo sch->cgrp_path, cgroup_id); 134786094b95STejun Heo return -EINVAL; 134886094b95STejun Heo } 134986094b95STejun Heo } else { 135086094b95STejun Heo target = sch; 135186094b95STejun Heo } 135286094b95STejun Heo 135386094b95STejun Heo /* 135486094b95STejun Heo * The target's caps storage may not be set up yet (e.g. a self-read 135586094b95STejun Heo * during ops.init_cids()). Pairs with the publish in 135686094b95STejun Heo * scx_alloc_pshards(): a non-NULL pshard has every element set. 135786094b95STejun Heo */ 135886094b95STejun Heo pshard = READ_ONCE(target->pshard); 135986094b95STejun Heo if (unlikely(!pshard)) { 136086094b95STejun Heo scx_error(sch, "scx_bpf_sub_caps() called before caps storage is initialized"); 136186094b95STejun Heo return -ENODEV; 136286094b95STejun Heo } 136386094b95STejun Heo 136486094b95STejun Heo ret = scx_cmask_ref_init(sch, out__ign, &ref); 136586094b95STejun Heo if (ret) { 136686094b95STejun Heo scx_error(sch, "invalid out (%d)", ret); 136786094b95STejun Heo return ret; 136886094b95STejun Heo } 136986094b95STejun Heo 137086094b95STejun Heo for (si = ref.shard_first; si < ref.shard_end; si++) { 137186094b95STejun Heo const struct scx_cid_shard *shard = &scx_cid_shard_ranges[si]; 137286094b95STejun Heo SCX_CMASK_DEFINE_SHARD(local_out, shard->base_cid, shard->nr_cids); 137386094b95STejun Heo u32 cap_bit; 137486094b95STejun Heo 137586094b95STejun Heo scx_for_each_cap_bit(cap_bit, caps) 137686094b95STejun Heo scx_cmask_or(local_out, &pshard[si]->caps[cap_bit].cmask); 137786094b95STejun Heo scx_cmask_ref_copy(&ref, local_out); 137886094b95STejun Heo } 137986094b95STejun Heo return 0; 138086094b95STejun Heo } 138186094b95STejun Heo 1382daf8e166STejun Heo __bpf_kfunc_end_defs(); 1383daf8e166STejun Heo 1384daf8e166STejun Heo #endif /* CONFIG_EXT_SUB_SCHED */ 1385