1daf8e166STejun Heo // SPDX-License-Identifier: GPL-2.0 2daf8e166STejun Heo /* 3daf8e166STejun Heo * BPF extensible scheduler class: Documentation/scheduler/sched-ext.rst 4daf8e166STejun Heo * 5daf8e166STejun Heo * Sub-scheduler hierarchy support. 6daf8e166STejun Heo * 7daf8e166STejun Heo * A sub-scheduler is an scx_sched attached to a cgroup subtree under another 8daf8e166STejun Heo * scx_sched. This file holds the sub-scheduler implementation: the scheduler 9daf8e166STejun Heo * tree walk, capability delegation, per-shard cap state and its sync, and the 10daf8e166STejun Heo * sub-scheduler enable/disable paths. The core dispatch/enqueue machinery it 11daf8e166STejun Heo * builds on lives in ext.c. 12daf8e166STejun Heo * 13daf8e166STejun Heo * Copyright (c) 2026 Meta Platforms, Inc. and affiliates. 14daf8e166STejun Heo * Copyright (c) 2026 Tejun Heo <tj@kernel.org> 15daf8e166STejun Heo */ 16daf8e166STejun Heo #include <linux/rhashtable.h> 17daf8e166STejun Heo #include "internal.h" 18daf8e166STejun Heo #include "cid.h" 19daf8e166STejun Heo #include "arena.h" 20daf8e166STejun Heo #include "sub.h" 217f480f34STejun Heo #include "inlines.h" 22daf8e166STejun Heo 23daf8e166STejun Heo #ifdef CONFIG_EXT_SUB_SCHED 24daf8e166STejun Heo 258946dbd3STejun Heo /* 268946dbd3STejun Heo * On while any sub-scheduler exists so that a root-only system doesn't pay for 278946dbd3STejun Heo * the sub-sched portions of hot paths. See scx_has_subs(). 288946dbd3STejun Heo */ 298946dbd3STejun Heo DEFINE_STATIC_KEY_FALSE(__scx_has_subs); 308946dbd3STejun Heo 315fd50174STejun Heo /* latched at root enable before any rescue runs */ 325fd50174STejun Heo static s32 scx_rescue_bw_1024; 335fd50174STejun Heo static s64 scx_rescue_quantum_ns; 345fd50174STejun Heo static s64 scx_rescue_sat_delta_ns; 35*bb70e4fbSTejun Heo static unsigned long scx_rescue_decay_halflife; 36*bb70e4fbSTejun Heo static unsigned long scx_rescue_overload_after; 375fd50174STejun Heo 38daf8e166STejun Heo /** 39bbda59d8STejun Heo * scx_skip_subtree_pre - Skip @pos's subtree in a pre-order walk 40bbda59d8STejun Heo * @pos: current position 41bbda59d8STejun Heo * @root: walk root 42bbda59d8STejun Heo * 43bbda59d8STejun Heo * In a walk started by scx_next_descendant_pre(), continue past @pos's subtree: 44bbda59d8STejun Heo * return @pos's next sibling, or the closest ancestor's next sibling, or NULL 45bbda59d8STejun Heo * if @pos's subtree is the last under @root. Same locking rules. 46bbda59d8STejun Heo */ 47bbda59d8STejun Heo struct scx_sched *scx_skip_subtree_pre(struct scx_sched *pos, struct scx_sched *root) 48bbda59d8STejun Heo { 49bbda59d8STejun Heo struct scx_sched *next; 50bbda59d8STejun Heo 51bbda59d8STejun Heo lockdep_assert(lockdep_is_held(&scx_enable_mutex) || 52bbda59d8STejun Heo lockdep_is_held(&scx_sched_lock) || 53bbda59d8STejun Heo rcu_read_lock_any_held()); 54bbda59d8STejun Heo 55bbda59d8STejun Heo while (pos != root) { 56bbda59d8STejun Heo next = list_next_or_null_rcu(&scx_parent(pos)->children, &pos->sibling, 57bbda59d8STejun Heo struct scx_sched, sibling); 58bbda59d8STejun Heo if (next) 59bbda59d8STejun Heo return next; 60bbda59d8STejun Heo pos = scx_parent(pos); 61bbda59d8STejun Heo } 62bbda59d8STejun Heo return NULL; 63bbda59d8STejun Heo } 64bbda59d8STejun Heo 65bbda59d8STejun Heo /** 66daf8e166STejun Heo * scx_next_descendant_pre - find the next descendant for pre-order walk 67daf8e166STejun Heo * @pos: the current position (%NULL to initiate traversal) 68daf8e166STejun Heo * @root: sched whose descendants to walk 69daf8e166STejun Heo * 70daf8e166STejun Heo * To be used by scx_for_each_descendant_pre(). Find the next descendant to 71daf8e166STejun Heo * visit for pre-order traversal of @root's descendants. @root is included in 72daf8e166STejun Heo * the iteration and the first node to be visited. 73daf8e166STejun Heo */ 74daf8e166STejun Heo struct scx_sched *scx_next_descendant_pre(struct scx_sched *pos, struct scx_sched *root) 75daf8e166STejun Heo { 76daf8e166STejun Heo struct scx_sched *next; 77daf8e166STejun Heo 78daf8e166STejun Heo lockdep_assert(lockdep_is_held(&scx_enable_mutex) || 7970f8b178STejun Heo lockdep_is_held(&scx_sched_lock) || 8070f8b178STejun Heo rcu_read_lock_any_held()); 81daf8e166STejun Heo 82daf8e166STejun Heo /* if first iteration, visit @root */ 83daf8e166STejun Heo if (!pos) 84daf8e166STejun Heo return root; 85daf8e166STejun Heo 86daf8e166STejun Heo /* visit the first child if exists */ 8770f8b178STejun Heo next = list_first_or_null_rcu(&pos->children, struct scx_sched, sibling); 88daf8e166STejun Heo if (next) 89daf8e166STejun Heo return next; 90daf8e166STejun Heo 91daf8e166STejun Heo /* no child, visit my or the closest ancestor's next sibling */ 92bbda59d8STejun Heo return scx_skip_subtree_pre(pos, root); 93daf8e166STejun Heo } 94daf8e166STejun Heo 95daf8e166STejun Heo static struct scx_sched *scx_find_sub_sched(u64 cgroup_id) 96daf8e166STejun Heo { 97daf8e166STejun Heo return rhashtable_lookup(&scx_sched_hash, &cgroup_id, 98daf8e166STejun Heo scx_sched_hash_params); 99daf8e166STejun Heo } 100daf8e166STejun Heo 101daf8e166STejun Heo void scx_set_task_sched(struct task_struct *p, struct scx_sched *sch) 102daf8e166STejun Heo { 103daf8e166STejun Heo rcu_assign_pointer(p->scx.sched, sch); 104daf8e166STejun Heo } 105daf8e166STejun Heo 106daf8e166STejun Heo struct cgroup *sch_cgroup(struct scx_sched *sch) 107daf8e166STejun Heo { 108daf8e166STejun Heo return sch->cgrp; 109daf8e166STejun Heo } 110daf8e166STejun Heo 111daf8e166STejun Heo /* for each descendant of @cgrp including self, set ->scx_sched to @sch */ 112daf8e166STejun Heo void set_cgroup_sched(struct cgroup *cgrp, struct scx_sched *sch) 113daf8e166STejun Heo { 114daf8e166STejun Heo struct cgroup *pos; 115daf8e166STejun Heo struct cgroup_subsys_state *css; 116daf8e166STejun Heo 117daf8e166STejun Heo cgroup_for_each_live_descendant_pre(pos, css, cgrp) 118daf8e166STejun Heo rcu_assign_pointer(pos->scx_sched, sch); 119daf8e166STejun Heo } 120daf8e166STejun Heo 1218dba3bbdSTejun Heo static void free_pshard(struct scx_pshard *pshard) 1228dba3bbdSTejun Heo { 1235f2a9a4cSTejun Heo struct scx_caps_updated *cu; 1245f2a9a4cSTejun Heo 1255f2a9a4cSTejun Heo if (!pshard) 1265f2a9a4cSTejun Heo return; 1275f2a9a4cSTejun Heo cu = &pshard->caps_updated; 1285f2a9a4cSTejun Heo if (cu->cmask_arena_out) 1295f2a9a4cSTejun Heo scx_arena_free(pshard->sch, cu->cmask_arena_out, 1305f2a9a4cSTejun Heo struct_size_t(struct scx_cmask, bits, 1315f2a9a4cSTejun Heo SCX_CMASK_NR_WORDS(pshard->nr_cids))); 1328dba3bbdSTejun Heo kfree(pshard); 1338dba3bbdSTejun Heo } 1348dba3bbdSTejun Heo 1358dba3bbdSTejun Heo void scx_free_pshards(struct scx_sched *sch) 1368dba3bbdSTejun Heo { 1378dba3bbdSTejun Heo s32 si; 1388dba3bbdSTejun Heo 1398dba3bbdSTejun Heo if (!sch->pshard) 1408dba3bbdSTejun Heo return; 1418dba3bbdSTejun Heo for (si = 0; si < sch->nr_pshards; si++) 1428dba3bbdSTejun Heo free_pshard(sch->pshard[si]); 1438dba3bbdSTejun Heo kfree(sch->pshard); 1448dba3bbdSTejun Heo } 1458dba3bbdSTejun Heo 1468dba3bbdSTejun Heo static struct scx_pshard *alloc_pshard(struct scx_sched *sch, s32 shard_idx, s32 node) 1478dba3bbdSTejun Heo { 1483a773220STejun Heo const struct scx_cid_shard *shard = 1493a773220STejun Heo &rcu_dereference_protected(scx_cid_shard_ranges, 1503a773220STejun Heo lockdep_is_held(&scx_enable_mutex))[shard_idx]; 1515f2a9a4cSTejun Heo size_t cmask_size = struct_size_t(struct scx_cmask, bits, 1525f2a9a4cSTejun Heo SCX_CMASK_NR_WORDS(shard->nr_cids)); 15386094b95STejun Heo struct scx_pshard *pshard; 1545f2a9a4cSTejun Heo struct scx_caps_updated *cu; 15586094b95STejun Heo s32 i; 15686094b95STejun Heo 15786094b95STejun Heo pshard = kzalloc_node(sizeof(*pshard), GFP_KERNEL, node); 15886094b95STejun Heo if (!pshard) 15986094b95STejun Heo return NULL; 16086094b95STejun Heo 16186094b95STejun Heo raw_spin_lock_init(&pshard->lock); 16286094b95STejun Heo pshard->sch = sch; 1635f2a9a4cSTejun Heo pshard->base = shard->base_cid; 1645f2a9a4cSTejun Heo pshard->nr_cids = shard->nr_cids; 16586094b95STejun Heo 16686094b95STejun Heo for (i = 0; i < __SCX_NR_CAPS; i++) 16786094b95STejun Heo scx_cmask_init(&pshard->caps[i].cmask, shard->base_cid, shard->nr_cids); 16886094b95STejun Heo 1695f2a9a4cSTejun Heo cu = &pshard->caps_updated; 1705f2a9a4cSTejun Heo raw_spin_lock_init(&cu->lock); 1715f2a9a4cSTejun Heo INIT_LIST_HEAD(&cu->node_in_flight); 1725f2a9a4cSTejun Heo __scx_cmask_init(&cu->cmask, shard->base_cid, shard->nr_cids, SCX_CID_SHARD_MAX_CPUS); 1735f2a9a4cSTejun Heo 1745f2a9a4cSTejun Heo cu->cmask_arena_out = scx_arena_alloc(sch, cmask_size); 1755f2a9a4cSTejun Heo if (!cu->cmask_arena_out) { 1765f2a9a4cSTejun Heo free_pshard(pshard); 1775f2a9a4cSTejun Heo return NULL; 1785f2a9a4cSTejun Heo } 1795f2a9a4cSTejun Heo 1805f2a9a4cSTejun Heo scx_cmask_init(cu->cmask_arena_out, shard->base_cid, shard->nr_cids); 1815f2a9a4cSTejun Heo 18286094b95STejun Heo return pshard; 1838dba3bbdSTejun Heo } 1848dba3bbdSTejun Heo 1858dba3bbdSTejun Heo s32 scx_alloc_pshards(struct scx_sched *sch) 1868dba3bbdSTejun Heo { 1878dba3bbdSTejun Heo struct scx_pshard **pshard; 1883a773220STejun Heo s32 *shard_node; 1898dba3bbdSTejun Heo s32 si; 1908dba3bbdSTejun Heo 1918dba3bbdSTejun Heo if (!sch->is_cid_type || !sch->arena_pool) 1928dba3bbdSTejun Heo return 0; 1938dba3bbdSTejun Heo 1943a773220STejun Heo shard_node = rcu_dereference_protected(scx_shard_node, 1953a773220STejun Heo lockdep_is_held(&scx_enable_mutex)); 1963a773220STejun Heo 1978dba3bbdSTejun Heo pshard = kzalloc_objs(pshard[0], scx_nr_cid_shards, GFP_KERNEL); 1988dba3bbdSTejun Heo if (!pshard) 1998dba3bbdSTejun Heo return -ENOMEM; 2008dba3bbdSTejun Heo 2018dba3bbdSTejun Heo for (si = 0; si < scx_nr_cid_shards; si++) { 2023a773220STejun Heo pshard[si] = alloc_pshard(sch, si, shard_node[si]); 2038dba3bbdSTejun Heo if (!pshard[si]) { 2048dba3bbdSTejun Heo while (--si >= 0) 2058dba3bbdSTejun Heo free_pshard(pshard[si]); 2068dba3bbdSTejun Heo kfree(pshard); 2078dba3bbdSTejun Heo return -ENOMEM; 2088dba3bbdSTejun Heo } 2098dba3bbdSTejun Heo } 2108dba3bbdSTejun Heo 2118dba3bbdSTejun Heo sch->nr_pshards = scx_nr_cid_shards; 2128dba3bbdSTejun Heo /* 2138dba3bbdSTejun Heo * Publish only after every entry is built so a reader observing 2143a773220STejun Heo * @sch->pshard never sees a partially-filled array or unpublished cid 2153a773220STejun Heo * tables. Pair the store with a barrier and an acquire load on the 2163a773220STejun Heo * read side. 2178dba3bbdSTejun Heo */ 2188dba3bbdSTejun Heo smp_wmb(); 2198dba3bbdSTejun Heo WRITE_ONCE(sch->pshard, pshard); 2208dba3bbdSTejun Heo return 0; 2218dba3bbdSTejun Heo } 2228dba3bbdSTejun Heo 22386094b95STejun Heo /* 22486094b95STejun Heo * Seed the root's caps fully. Root owns all cids on all caps at enable time. 22586094b95STejun Heo * Children acquire caps via scx_bpf_sub_grant(). 22686094b95STejun Heo */ 22786094b95STejun Heo void scx_init_root_caps(struct scx_sched *sch) 22886094b95STejun Heo { 22986094b95STejun Heo s32 si, i; 23086094b95STejun Heo 23186094b95STejun Heo for (si = 0; si < sch->nr_pshards; si++) { 23286094b95STejun Heo struct scx_pshard *ps = sch->pshard[si]; 23386094b95STejun Heo 23486094b95STejun Heo for (i = 0; i < __SCX_NR_CAPS; i++) 23586094b95STejun Heo scx_cmask_fill(&ps->caps[i].cmask); 23686094b95STejun Heo } 23786094b95STejun Heo } 23886094b95STejun Heo 2395fd50174STejun Heo /* unserved remainder of @rq's rescuee's admitted slice, 0 once fully served */ 2405fd50174STejun Heo static s64 scx_rescue_slice_remaining(struct rq *rq) 2415fd50174STejun Heo { 2425fd50174STejun Heo s64 served = rq->scx.rescue.curr->se.sum_exec_runtime - rq->scx.rescue.exec_snap; 2435fd50174STejun Heo 2445fd50174STejun Heo return max(rq->scx.rescue.slice - served, 0); 2455fd50174STejun Heo } 2465fd50174STejun Heo 247*bb70e4fbSTejun Heo /* 248*bb70e4fbSTejun Heo * Decay @pcpu's rescue usage average in place, halving per the knob-derived 249*bb70e4fbSTejun Heo * halflife, see scx_rescue_set_knobs(). The timestamp advances only by whole 250*bb70e4fbSTejun Heo * halflives. 251*bb70e4fbSTejun Heo */ 252*bb70e4fbSTejun Heo static u64 scx_rescue_decay_avg(struct scx_sched_pcpu *pcpu) 253*bb70e4fbSTejun Heo { 254*bb70e4fbSTejun Heo unsigned long halflife = scx_rescue_decay_halflife; 255*bb70e4fbSTejun Heo u64 n = div_u64(get_jiffies_64() - pcpu->rescue_avg_at, halflife); 256*bb70e4fbSTejun Heo 257*bb70e4fbSTejun Heo if (n) { 258*bb70e4fbSTejun Heo pcpu->rescue_avg = n < 64 ? pcpu->rescue_avg >> n : 0; 259*bb70e4fbSTejun Heo pcpu->rescue_avg_at += n * halflife; 260*bb70e4fbSTejun Heo } 261*bb70e4fbSTejun Heo return pcpu->rescue_avg; 262*bb70e4fbSTejun Heo } 263*bb70e4fbSTejun Heo 26475a8c820STejun Heo /** 2655fd50174STejun Heo * scx_rescue_charge - Charge the rescuee's runtime 2665fd50174STejun Heo * @rq: rq the rescuee is running on 2675fd50174STejun Heo * @delta_exec: runtime being charged 2685fd50174STejun Heo * 2695fd50174STejun Heo * Also ends the rescue once the admitted slice has been served in full. Ending 2705fd50174STejun Heo * on served time rather than slice exhaustion bounds both the rescue and the 2715fd50174STejun Heo * charging when a scheduler extends the rescuee's slice. 2725fd50174STejun Heo */ 2735fd50174STejun Heo void scx_rescue_charge(struct rq *rq, s64 delta_exec) 2745fd50174STejun Heo { 275*bb70e4fbSTejun Heo struct scx_sched_pcpu *pcpu; 276*bb70e4fbSTejun Heo 2775fd50174STejun Heo lockdep_assert_rq_held(rq); 2785fd50174STejun Heo 2795fd50174STejun Heo /* 2805fd50174STejun Heo * A rescue slice is bounded by one quantum and tick-driven expiry can 2815fd50174STejun Heo * overshoot by up to a tick. Clamp to avoid wild over-charges on VMs. 2825fd50174STejun Heo */ 2835fd50174STejun Heo delta_exec = min_t(s64, delta_exec, scx_rescue_quantum_ns + TICK_NSEC); 2845fd50174STejun Heo 2855fd50174STejun Heo rq->scx.rescue.budget -= delta_exec; 2865fd50174STejun Heo 287*bb70e4fbSTejun Heo /* per-cpu usage average feeds the overload victim pick */ 288*bb70e4fbSTejun Heo pcpu = per_cpu_ptr(scx_task_sched(rq->curr)->pcpu, cpu_of(rq)); 289*bb70e4fbSTejun Heo pcpu->rescue_avg = scx_rescue_decay_avg(pcpu) + delta_exec; 290*bb70e4fbSTejun Heo 2915fd50174STejun Heo if (!scx_rescue_slice_remaining(rq)) 2925fd50174STejun Heo scx_task_slice_ended(rq, rq->scx.rescue.curr); 2935fd50174STejun Heo } 2945fd50174STejun Heo 2955fd50174STejun Heo /** 2965fd50174STejun Heo * scx_rescue_end - End the rescue execution on @rq 2975fd50174STejun Heo * @rq: rq of interest 2985fd50174STejun Heo * 2995fd50174STejun Heo * When no rescuee is left pending, the session is over and the balance above 3005fd50174STejun Heo * one quantum dies with it - it would otherwise become a banked license to 3015fd50174STejun Heo * preempt the cid owner long after the starvation ended. While waiters remain, 3025fd50174STejun Heo * the accrued deficit belongs to the queue and carries into the next rescue. 3035fd50174STejun Heo */ 3045fd50174STejun Heo void scx_rescue_end(struct rq *rq) 3055fd50174STejun Heo { 3065fd50174STejun Heo lockdep_assert_rq_held(rq); 3075fd50174STejun Heo 3085fd50174STejun Heo rq->scx.rescue.curr = NULL; 3095fd50174STejun Heo if (list_empty(&rq->scx.rescue.dsq.list)) 3105fd50174STejun Heo rq->scx.rescue.budget = min(rq->scx.rescue.budget, scx_rescue_quantum_ns); 3115fd50174STejun Heo } 3125fd50174STejun Heo 3135fd50174STejun Heo /** 3145fd50174STejun Heo * scx_rescue_keep - Keep the rescue going for a preempted-out rescuee 3155fd50174STejun Heo * @rq: rq @p is running on 3165fd50174STejun Heo * @p: task under rescue whose slice is exhausted 3175fd50174STejun Heo * 3185fd50174STejun Heo * Called from put_prev_task_scx() to decide what an exhausted slice means for 3195fd50174STejun Heo * the rescuee. scx_rescue_charge() ends the rescue the moment the admitted 3205fd50174STejun Heo * slice is fully served, so arriving here with the rescue still open means @p 3215fd50174STejun Heo * was preempted. Restore the unserved remainder and return %true - @p stays the 3225fd50174STejun Heo * rescuee and the caller reinserts it at the tail of the local DSQ, behind 3235fd50174STejun Heo * whatever preempted the rescuee. 3245fd50174STejun Heo * 3255fd50174STejun Heo * Return %false to end the rescue instead - the slice is already fully served, 3265fd50174STejun Heo * @p is leaving the rq or bypass is dismantling rescues. 3275fd50174STejun Heo */ 3285fd50174STejun Heo bool scx_rescue_keep(struct rq *rq, struct task_struct *p) 3295fd50174STejun Heo { 3305fd50174STejun Heo s64 remaining = scx_rescue_slice_remaining(rq); 3315fd50174STejun Heo 3325fd50174STejun Heo lockdep_assert_rq_held(rq); 3335fd50174STejun Heo 3345fd50174STejun Heo if (!remaining || !(p->scx.flags & SCX_TASK_QUEUED) || 3355fd50174STejun Heo scx_bypassing(scx_task_sched(p), cpu_of(rq))) 3365fd50174STejun Heo return false; 3375fd50174STejun Heo 3385fd50174STejun Heo scx_set_task_slice(p, remaining); 3395fd50174STejun Heo return true; 3405fd50174STejun Heo } 3415fd50174STejun Heo 3425fd50174STejun Heo /** 3435fd50174STejun Heo * scx_rescue_accrue - Accrue budget at the configured fraction of elapsed time 3445fd50174STejun Heo * @rq: rq of interest 3455fd50174STejun Heo * 3465fd50174STejun Heo * A session spans from the first arrival until no rescuee is left, pending or 3475fd50174STejun Heo * admitted. While one is active the cap is three quanta and the balance drives 3485fd50174STejun Heo * escalation, see scx_rescue_timerfn(). Outside a session the cap is one 3495fd50174STejun Heo * quantum, so an idle gap funds the next arrival's admission but never an 3505fd50174STejun Heo * escalation. 3515fd50174STejun Heo */ 3525fd50174STejun Heo static void scx_rescue_accrue(struct rq *rq) 3535fd50174STejun Heo { 3545fd50174STejun Heo bool in_session = rq->scx.rescue.curr || !list_empty(&rq->scx.rescue.dsq.list); 3555fd50174STejun Heo s64 cap = in_session ? 3 * scx_rescue_quantum_ns : scx_rescue_quantum_ns; 3565fd50174STejun Heo s64 delta; 3575fd50174STejun Heo u64 now; 3585fd50174STejun Heo 3595fd50174STejun Heo lockdep_assert_rq_held(rq); 3605fd50174STejun Heo 3615fd50174STejun Heo /* not every path here holds an updated rq clock, use __scx_bpf_now() */ 3625fd50174STejun Heo now = __scx_bpf_now(rq); 3635fd50174STejun Heo delta = now - rq->scx.rescue.clock; 3645fd50174STejun Heo rq->scx.rescue.clock = now; 3655fd50174STejun Heo 3665fd50174STejun Heo /* 3675fd50174STejun Heo * Avoid multiplication overflows by taking a shortcut when the gap is 3685fd50174STejun Heo * large enough to fill the budget. 3695fd50174STejun Heo */ 3705fd50174STejun Heo if (delta >= scx_rescue_sat_delta_ns) 3715fd50174STejun Heo rq->scx.rescue.budget = cap; 3725fd50174STejun Heo else 3735fd50174STejun Heo rq->scx.rescue.budget = 3745fd50174STejun Heo min(cap, rq->scx.rescue.budget + 3755fd50174STejun Heo ((delta * scx_rescue_bw_1024) >> SCHED_CAPACITY_SHIFT)); 3765fd50174STejun Heo } 3775fd50174STejun Heo 3785fd50174STejun Heo /* 3795fd50174STejun Heo * The slice for the next admission - the quantum divided across the stranded 3805fd50174STejun Heo * tasks so that a crowded queue round-robins on shorter slices. 3815fd50174STejun Heo */ 3825fd50174STejun Heo static s64 scx_rescue_next_slice(struct rq *rq) 3835fd50174STejun Heo { 3845fd50174STejun Heo s64 min_slice = max_t(s64, SCX_RESCUE_MIN_SLICE_US * NSEC_PER_USEC, TICK_NSEC); 3855fd50174STejun Heo u32 depth = rq->scx.rescue.dsq.nr ?: 1; 3865fd50174STejun Heo 3875fd50174STejun Heo return clamp(div_s64(scx_rescue_quantum_ns, depth), min_slice, scx_rescue_quantum_ns); 3885fd50174STejun Heo } 3895fd50174STejun Heo 3905fd50174STejun Heo static void scx_rescue_timer_arm(struct rq *rq) 3915fd50174STejun Heo { 3925fd50174STejun Heo struct timer_list *timer = &rq->scx.rescue.timer; 3935fd50174STejun Heo s64 delay = scx_rescue_quantum_ns / 4; /* should be granular enough */ 3945fd50174STejun Heo 3955fd50174STejun Heo if (timer_pending(timer)) 3965fd50174STejun Heo return; 3975fd50174STejun Heo 3985fd50174STejun Heo /* 3995fd50174STejun Heo * While the head waiter can't be admitted because the bucket is short 4005fd50174STejun Heo * of a full quantum, stretch to the full funding delay. 4015fd50174STejun Heo */ 4025fd50174STejun Heo if (!rq->scx.rescue.curr && rq->scx.rescue.budget < scx_rescue_quantum_ns) { 4035fd50174STejun Heo s64 deficit = scx_rescue_quantum_ns - rq->scx.rescue.budget; 4045fd50174STejun Heo 4055fd50174STejun Heo delay = max(delay, 4065fd50174STejun Heo div_s64(deficit << SCHED_CAPACITY_SHIFT, scx_rescue_bw_1024)); 4075fd50174STejun Heo } 4085fd50174STejun Heo 4095fd50174STejun Heo /* +1 rounds up so the beat is due by the time the timer fires */ 4105fd50174STejun Heo timer->expires = jiffies + nsecs_to_jiffies(delay) + 1; 4115fd50174STejun Heo add_timer_on(timer, cpu_of(rq)); 4125fd50174STejun Heo } 4135fd50174STejun Heo 4145fd50174STejun Heo /** 4155fd50174STejun Heo * scx_rescue_admit - Start rescuing @p on @rq 4165fd50174STejun Heo * @rq: rq @p is being admitted on 4175fd50174STejun Heo * @p: task being admitted, off any DSQ 4185fd50174STejun Heo * @slice: CPU time to grant 4195fd50174STejun Heo * 4205fd50174STejun Heo * The schedulers keep their normal control over @p and may preempt or reslice 4215fd50174STejun Heo * it. @slice is measured on served CPU time against the snapshot taken here, so 4225fd50174STejun Heo * neither shortens the rescue, see scx_rescue_charge() and scx_rescue_keep(). 4235fd50174STejun Heo * Prolonged denial escalates into protected execution, see 4245fd50174STejun Heo * scx_rescue_timerfn(). 4255fd50174STejun Heo */ 4265fd50174STejun Heo static void scx_rescue_admit(struct rq *rq, struct task_struct *p, s64 slice) 4275fd50174STejun Heo { 4285fd50174STejun Heo lockdep_assert_rq_held(rq); 4295fd50174STejun Heo WARN_ON_ONCE(rq->scx.rescue.curr); 4305fd50174STejun Heo 4315fd50174STejun Heo rq->scx.rescue.curr = p; 4325fd50174STejun Heo rq->scx.rescue.slice = slice; 4335fd50174STejun Heo rq->scx.rescue.exec_snap = p->se.sum_exec_runtime; 4345fd50174STejun Heo scx_set_task_slice(p, slice); 4355fd50174STejun Heo scx_rescue_timer_arm(rq); 4365fd50174STejun Heo } 4375fd50174STejun Heo 4385fd50174STejun Heo /** 4395fd50174STejun Heo * scx_rescue_try_admit - Try to admit a freshly stranded task 4405fd50174STejun Heo * @rq: rq @p is being inserted on 4415fd50174STejun Heo * @p: stranded task being diverted to rescue 4425fd50174STejun Heo * 4435fd50174STejun Heo * One rescue at a time and earlier arrivals go first. Admission needs a full 4445fd50174STejun Heo * quantum of budget, spent as the rescue runs. Return %true if @p was admitted 4455fd50174STejun Heo * and should be inserted at the tail of @rq's local DSQ, %false if it has to 4465fd50174STejun Heo * park on the rescue DSQ, with the timer armed to admit it later. 4475fd50174STejun Heo */ 4485fd50174STejun Heo static bool scx_rescue_try_admit(struct rq *rq, struct task_struct *p) 4495fd50174STejun Heo { 4505fd50174STejun Heo scx_rescue_accrue(rq); 4515fd50174STejun Heo 4525fd50174STejun Heo if (!rq->scx.rescue.curr && list_empty(&rq->scx.rescue.dsq.list) && 4535fd50174STejun Heo rq->scx.rescue.budget >= scx_rescue_quantum_ns) { 4545fd50174STejun Heo scx_rescue_admit(rq, p, scx_rescue_quantum_ns); 4555fd50174STejun Heo return true; 4565fd50174STejun Heo } 4575fd50174STejun Heo 4585fd50174STejun Heo scx_rescue_timer_arm(rq); 4595fd50174STejun Heo return false; 4605fd50174STejun Heo } 4615fd50174STejun Heo 4625fd50174STejun Heo /** 463*bb70e4fbSTejun Heo * scx_rescue_check_overload - Eject the top rescue consumer on a stuck rescue 464*bb70e4fbSTejun Heo * @rq: rq whose rescue timer fired 465*bb70e4fbSTejun Heo * 466*bb70e4fbSTejun Heo * If the oldest waiter on @rq's rescue DSQ has been queued for too long, rescue 467*bb70e4fbSTejun Heo * demand on this cpu persistently exceeds the configured bandwidth. Eject the 468*bb70e4fbSTejun Heo * sub with the highest recent rescue consumption instead of letting the 469*bb70e4fbSTejun Heo * scheduler stall path blame the waiter's owner, who may just be crowded out. 470*bb70e4fbSTejun Heo */ 471*bb70e4fbSTejun Heo static void scx_rescue_check_overload(struct rq *rq) 472*bb70e4fbSTejun Heo { 473*bb70e4fbSTejun Heo struct scx_sched *victim = NULL, *pos; 474*bb70e4fbSTejun Heo struct task_struct *p; 475*bb70e4fbSTejun Heo int cpu = cpu_of(rq); 476*bb70e4fbSTejun Heo u64 max_avg = 0; 477*bb70e4fbSTejun Heo u32 dur_ms; 478*bb70e4fbSTejun Heo 479*bb70e4fbSTejun Heo lockdep_assert_rq_held(rq); 480*bb70e4fbSTejun Heo 481*bb70e4fbSTejun Heo p = list_first_entry_or_null(&rq->scx.rescue.dsq.list, struct task_struct, 482*bb70e4fbSTejun Heo scx.dsq_list.node); 483*bb70e4fbSTejun Heo if (!p) 484*bb70e4fbSTejun Heo return; 485*bb70e4fbSTejun Heo 486*bb70e4fbSTejun Heo /* has the head waiter been queued for longer than the threshold? */ 487*bb70e4fbSTejun Heo if (time_before(jiffies, p->scx.rescue_at + scx_rescue_overload_after)) 488*bb70e4fbSTejun Heo return; 489*bb70e4fbSTejun Heo 490*bb70e4fbSTejun Heo /* 491*bb70e4fbSTejun Heo * Grace period after the last ejection on this cpu - the freed 492*bb70e4fbSTejun Heo * bandwidth gets one threshold's worth of time to drain the backlog 493*bb70e4fbSTejun Heo * before another sub is judged. 494*bb70e4fbSTejun Heo */ 495*bb70e4fbSTejun Heo if (time_before64(get_jiffies_64(), rq->scx.rescue.kill_at + 496*bb70e4fbSTejun Heo scx_rescue_overload_after)) 497*bb70e4fbSTejun Heo return; 498*bb70e4fbSTejun Heo 499*bb70e4fbSTejun Heo list_for_each_entry_rcu(pos, &scx_sched_all, all) { 500*bb70e4fbSTejun Heo u64 avg = scx_rescue_decay_avg(per_cpu_ptr(pos->pcpu, cpu)); 501*bb70e4fbSTejun Heo 502*bb70e4fbSTejun Heo /* skip an already-exiting sub, else the ejection is wasted */ 503*bb70e4fbSTejun Heo if (pos->level && avg > max_avg && 504*bb70e4fbSTejun Heo atomic_read(&pos->exit_kind) == SCX_EXIT_NONE) { 505*bb70e4fbSTejun Heo max_avg = avg; 506*bb70e4fbSTejun Heo victim = pos; 507*bb70e4fbSTejun Heo } 508*bb70e4fbSTejun Heo } 509*bb70e4fbSTejun Heo if (!victim) 510*bb70e4fbSTejun Heo return; 511*bb70e4fbSTejun Heo 512*bb70e4fbSTejun Heo rq->scx.rescue.kill_at = get_jiffies_64(); 513*bb70e4fbSTejun Heo dur_ms = jiffies_to_msecs(jiffies - p->scx.rescue_at); 514*bb70e4fbSTejun Heo __scx_exit(victim, SCX_EXIT_ERROR_RESCUE, 0, cpu, 515*bb70e4fbSTejun Heo "used too much rescue CPU time (%llums) while %s[%d] waited %u.%03us to be rescued", 516*bb70e4fbSTejun Heo div_u64(max_avg, NSEC_PER_MSEC), p->comm, p->pid, dur_ms / 1000, 517*bb70e4fbSTejun Heo dur_ms % 1000); 518*bb70e4fbSTejun Heo } 519*bb70e4fbSTejun Heo 520*bb70e4fbSTejun Heo /** 5215fd50174STejun Heo * scx_rescue_timerfn - Drive and pace rescue execution 5225fd50174STejun Heo * @timer: rq->scx.rescue.timer 5235fd50174STejun Heo * 5245fd50174STejun Heo * Runs every quarter quantum while a rescuee exists, pending or admitted, see 5255fd50174STejun Heo * scx_rescue_timer_arm(). The head waiter is admitted once the bucket holds a 5265fd50174STejun Heo * full quantum and granted its slice, see scx_rescue_next_slice(). A session 5275fd50174STejun Heo * whose budget accumulates over two quanta with the admitted rescuee still 5285fd50174STejun Heo * waiting escalates - the rescuee's remaining slice turns into protected 529*bb70e4fbSTejun Heo * execution and it preempts the current task. An overloaded rescue queue ejects 530*bb70e4fbSTejun Heo * the top consumer, see scx_rescue_check_overload(). 5315fd50174STejun Heo */ 5325fd50174STejun Heo static void scx_rescue_timerfn(struct timer_list *timer) 5335fd50174STejun Heo { 5345fd50174STejun Heo struct rq *rq = timer_container_of(rq, timer, scx.rescue.timer); 5355fd50174STejun Heo struct task_struct *p; 5365fd50174STejun Heo 5375fd50174STejun Heo guard(rq_lock_irqsave)(rq); 5385fd50174STejun Heo 5395fd50174STejun Heo p = rq->scx.rescue.curr; 5405fd50174STejun Heo if (!p && list_empty(&rq->scx.rescue.dsq.list)) 5415fd50174STejun Heo return; 5425fd50174STejun Heo 5435fd50174STejun Heo scx_rescue_accrue(rq); 544*bb70e4fbSTejun Heo scx_rescue_check_overload(rq); 5455fd50174STejun Heo 5465fd50174STejun Heo if (!p) { 5475fd50174STejun Heo s64 slice = scx_rescue_next_slice(rq); 5485fd50174STejun Heo 5495fd50174STejun Heo /* no rescue in progress */ 5505fd50174STejun Heo if (rq->scx.rescue.budget < scx_rescue_quantum_ns) 5515fd50174STejun Heo goto out_arm; 5525fd50174STejun Heo 5535fd50174STejun Heo /* there's enough budget to start rescuing the next one */ 5545fd50174STejun Heo p = list_first_entry(&rq->scx.rescue.dsq.list, struct task_struct, 5555fd50174STejun Heo scx.dsq_list.node); 5565fd50174STejun Heo scx_task_unlink_from_dsq(p, &rq->scx.rescue.dsq); 5575fd50174STejun Heo scx_rescue_admit(rq, p, slice); 5585fd50174STejun Heo scx_move_local_task_to_local_dsq(scx_task_sched(p), p, SCX_ENQ_IGNORE_CAPS, 5595fd50174STejun Heo &rq->scx.rescue.dsq, rq); 5605fd50174STejun Heo if (sched_class_above(&ext_sched_class, rq->curr->sched_class)) 5615fd50174STejun Heo resched_curr(rq); 5625fd50174STejun Heo } else if (p->scx.dsq && rq->scx.rescue.budget > 2 * scx_rescue_quantum_ns) { 5635fd50174STejun Heo /* 5645fd50174STejun Heo * The rescuee waited for the CPU for too long. Escalate - grant 5655fd50174STejun Heo * the unserved remainder, protect it from the schedulers and 5665fd50174STejun Heo * preempt the current task. The slice is set before the 5675fd50174STejun Heo * protection. Repeat beats only repeat the head move - the 5685fd50174STejun Heo * slice write is refused on a protected task. 5695fd50174STejun Heo */ 5705fd50174STejun Heo scx_set_task_slice(p, scx_rescue_slice_remaining(rq)); 5715fd50174STejun Heo p->scx.flags |= SCX_TASK_PROTECTED; 5725fd50174STejun Heo scx_task_unlink_from_dsq(p, &rq->scx.local_dsq); 5735fd50174STejun Heo scx_move_local_task_to_local_dsq(scx_task_sched(p), p, 5745fd50174STejun Heo SCX_ENQ_HEAD | SCX_ENQ_PREEMPT | SCX_ENQ_IGNORE_CAPS, 5755fd50174STejun Heo &rq->scx.local_dsq, rq); 5765fd50174STejun Heo } 5775fd50174STejun Heo out_arm: 5785fd50174STejun Heo scx_rescue_timer_arm(rq); 5795fd50174STejun Heo } 5805fd50174STejun Heo 5815fd50174STejun Heo /* flush out tasks waiting for rescue before a CPU goes down */ 5825fd50174STejun Heo void scx_rescue_flush(struct rq *rq) 5835fd50174STejun Heo { 5845fd50174STejun Heo struct task_struct *p, *n; 5855fd50174STejun Heo 5865fd50174STejun Heo lockdep_assert_rq_held(rq); 5875fd50174STejun Heo 5885fd50174STejun Heo /* sched domain rebuilds call rq_offline with the CPU staying alive */ 5895fd50174STejun Heo if (cpu_active(cpu_of(rq))) 5905fd50174STejun Heo return; 5915fd50174STejun Heo 5925fd50174STejun Heo /* end the current rescue */ 5935fd50174STejun Heo if (rq->scx.rescue.curr) 5945fd50174STejun Heo scx_task_slice_ended(rq, rq->scx.rescue.curr); 5955fd50174STejun Heo 5965fd50174STejun Heo /* and flush out all pending ones */ 5975fd50174STejun Heo list_for_each_entry_safe(p, n, &rq->scx.rescue.dsq.list, scx.dsq_list.node) { 5985fd50174STejun Heo scx_task_unlink_from_dsq(p, &rq->scx.rescue.dsq); 5995fd50174STejun Heo scx_move_local_task_to_local_dsq(scx_task_sched(p), p, SCX_ENQ_IGNORE_CAPS, 6005fd50174STejun Heo &rq->scx.rescue.dsq, rq); 6015fd50174STejun Heo } 6025fd50174STejun Heo 6035fd50174STejun Heo timer_delete(&rq->scx.rescue.timer); 6045fd50174STejun Heo } 6055fd50174STejun Heo 6065fd50174STejun Heo void scx_rescue_dump(struct seq_buf *s, struct rq *rq) 6075fd50174STejun Heo { 6085fd50174STejun Heo struct task_struct *p = rq->scx.rescue.curr; 6095fd50174STejun Heo 6105fd50174STejun Heo scx_dump_line(s, " rescue=%u budget=%lldus rescuing=%s[%d]", 6115fd50174STejun Heo rq->scx.rescue.dsq.nr, 6125fd50174STejun Heo div_s64(rq->scx.rescue.budget, NSEC_PER_USEC), 6135fd50174STejun Heo p ? p->comm : "none", p ? p->pid : -1); 6145fd50174STejun Heo } 6155fd50174STejun Heo 616*bb70e4fbSTejun Heo /* 617*bb70e4fbSTejun Heo * A scheduler whose stall watchdog is shorter than the overload threshold gets 618*bb70e4fbSTejun Heo * stall-killed over its parked waiters before the overload check can eject the 619*bb70e4fbSTejun Heo * actual top consumer. The root's knobs set the threshold, warn on any 620*bb70e4fbSTejun Heo * scheduler that doesn't fit it. 621*bb70e4fbSTejun Heo */ 622*bb70e4fbSTejun Heo static void scx_rescue_check_timeout(struct scx_sched *sch) 623*bb70e4fbSTejun Heo { 624*bb70e4fbSTejun Heo if (!scx_rescue_bw_1024 || sch->watchdog_timeout > scx_rescue_overload_after) 625*bb70e4fbSTejun Heo return; 626*bb70e4fbSTejun Heo 627*bb70e4fbSTejun Heo pr_warn("sched_ext: %s: watchdog timeout %ums <= rescue overload threshold %ums\n", 628*bb70e4fbSTejun Heo sch->ops.name, jiffies_to_msecs(sch->watchdog_timeout), 629*bb70e4fbSTejun Heo jiffies_to_msecs(scx_rescue_overload_after)); 630*bb70e4fbSTejun Heo } 631*bb70e4fbSTejun Heo 6325fd50174STejun Heo /* latch the rescue parameters on root scheduler enable */ 6335fd50174STejun Heo void scx_rescue_set_knobs(struct scx_sched *sch) 6345fd50174STejun Heo { 6355fd50174STejun Heo s32 bw_ppt = sch->ops.rescue_bandwidth_ppt ?: SCX_RESCUE_DFL_BW_PPT; 6365fd50174STejun Heo s64 quantum_us = sch->ops.rescue_quantum_us ?: SCX_RESCUE_DFL_QUANTUM_US; 637*bb70e4fbSTejun Heo s64 period_ns; 6385fd50174STejun Heo 6395fd50174STejun Heo if (sch->ops.rescue_bandwidth_ppt == SCX_RESCUE_DISABLE) { 6405fd50174STejun Heo scx_rescue_bw_1024 = 0; 6415fd50174STejun Heo return; 6425fd50174STejun Heo } 6435fd50174STejun Heo 6445fd50174STejun Heo scx_rescue_bw_1024 = bw_ppt * SCHED_CAPACITY_SCALE / 1000; 6455fd50174STejun Heo scx_rescue_quantum_ns = max(quantum_us * NSEC_PER_USEC, TICK_NSEC); 6465fd50174STejun Heo scx_rescue_sat_delta_ns = 6475fd50174STejun Heo div_s64((4 * scx_rescue_quantum_ns + TICK_NSEC) << SCHED_CAPACITY_SHIFT, 6485fd50174STejun Heo scx_rescue_bw_1024); 6495fd50174STejun Heo 6505fd50174STejun Heo /* 651*bb70e4fbSTejun Heo * The overload threshold and the decay halflife scale with the funding 652*bb70e4fbSTejun Heo * period - the time the bucket takes to fund one full quantum. 6535fd50174STejun Heo */ 654*bb70e4fbSTejun Heo period_ns = div_s64(scx_rescue_quantum_ns << SCHED_CAPACITY_SHIFT, scx_rescue_bw_1024); 655*bb70e4fbSTejun Heo scx_rescue_overload_after = 656*bb70e4fbSTejun Heo clamp(nsecs_to_jiffies(SCX_RESCUE_OVERLOAD_MULT * period_ns), 657*bb70e4fbSTejun Heo msecs_to_jiffies(SCX_RESCUE_MIN_OVERLOAD_MS), 658*bb70e4fbSTejun Heo msecs_to_jiffies(SCX_RESCUE_MAX_OVERLOAD_MS)); 659*bb70e4fbSTejun Heo scx_rescue_decay_halflife = scx_rescue_overload_after / 4; 660*bb70e4fbSTejun Heo 661*bb70e4fbSTejun Heo /* a single in-budget wait must not cross the overload trigger */ 662*bb70e4fbSTejun Heo if (nsecs_to_jiffies(period_ns) > scx_rescue_overload_after / 2) 663*bb70e4fbSTejun Heo pr_warn("sched_ext: %s: rescue funding period %lldms > overload threshold %ums / 2\n", 664*bb70e4fbSTejun Heo sch->ops.name, div_s64(period_ns, NSEC_PER_MSEC), 665*bb70e4fbSTejun Heo jiffies_to_msecs(scx_rescue_overload_after)); 666*bb70e4fbSTejun Heo 667*bb70e4fbSTejun Heo scx_rescue_check_timeout(sch); 6685fd50174STejun Heo } 6695fd50174STejun Heo 6705fd50174STejun Heo void scx_rescue_init(struct rq *rq) 6715fd50174STejun Heo { 6725fd50174STejun Heo BUG_ON(scx_init_dsq(&rq->scx.rescue.dsq, SCX_DSQ_RESCUE, NULL)); 6735fd50174STejun Heo timer_setup(&rq->scx.rescue.timer, scx_rescue_timerfn, TIMER_PINNED); 674*bb70e4fbSTejun Heo rq->scx.rescue.kill_at = get_jiffies_64(); 6755fd50174STejun Heo } 6765fd50174STejun Heo 6775fd50174STejun Heo /** 6785fd50174STejun Heo * scx_resolve_local_dsq - Pick the local, rescue or reject DSQ for an insert 67975a8c820STejun Heo * @sch: enqueuing sub-sched 68075a8c820STejun Heo * @rq: rq whose local DSQ @p targets 68175a8c820STejun Heo * @p: task being inserted 6826ea3be36STejun Heo * @enq_flags: in/out, unhonored flags are cleared 68375a8c820STejun Heo * 6845fd50174STejun Heo * Return @rq's local DSQ if @sch holds the required caps on @rq's cid. 6855fd50174STejun Heo * Otherwise, return @rq's rescue DSQ if the insert carries %SCX_ENQ_RESCUE and 6865fd50174STejun Heo * rescue is enabled, or @rq's reject DSQ after recording the reenq reason on 6875fd50174STejun Heo * @p. 68875a8c820STejun Heo * 6895fd50174STejun Heo * %SCX_ENQ_IMMED, %SCX_ENQ_PREEMPT and %SCX_ENQ_HEAD are cleared when diverting 6905fd50174STejun Heo * to rescue or reject. %SCX_ENQ_PREEMPT is also cleared on a fallback 6915fd50174STejun Heo * migration-disabled admission. 6926ea3be36STejun Heo * 69375a8c820STejun Heo * Bypass doesn't need special-casing as a bypassing sched's tasks are enqueued 69475a8c820STejun Heo * to and run by its nearest non-bypassing ancestor. If root is bypassing, it 69575a8c820STejun Heo * always holds all caps. 69675a8c820STejun Heo */ 6978b3b8522STejun Heo struct scx_dispatch_q *scx_resolve_local_dsq(struct scx_sched *sch, struct rq *rq, 69875a8c820STejun Heo struct task_struct *p, u64 *enq_flags) 69975a8c820STejun Heo { 7008946dbd3STejun Heo if (!scx_has_subs()) 7018946dbd3STejun Heo return &rq->scx.local_dsq; 7028946dbd3STejun Heo 70375a8c820STejun Heo s32 cid = __scx_cpu_to_cid(cpu_of(rq)); 704f2c9f515STejun Heo struct scx_sched *asch = rq->scx.remote_activate_sch ?: sch; 7056ea3be36STejun Heo u64 needed = scx_caps_for_enq(*enq_flags); 7066ea3be36STejun Heo u64 missing; 7076ea3be36STejun Heo 708f2c9f515STejun Heo /* 709f2c9f515STejun Heo * On a remote activation the scheduling sched (@asch) differs from 710f2c9f515STejun Heo * @p's owner (@sch). Check caps against the scheduling sched. 711f2c9f515STejun Heo */ 7126ea3be36STejun Heo if (*enq_flags & SCX_ENQ_PREEMPT) 71378f8d726STejun Heo needed |= scx_caps_for_preempt(asch, rq, *enq_flags); 714f2c9f515STejun Heo missing = scx_missing_caps(asch, cpu_of(rq), needed); 71575a8c820STejun Heo 71675a8c820STejun Heo /* requirements met */ 71775a8c820STejun Heo if (likely(!missing)) 71875a8c820STejun Heo return &rq->scx.local_dsq; 71975a8c820STejun Heo 72075a8c820STejun Heo /* 72175a8c820STejun Heo * The task must run on this CPU regardless of caps: the rq is draining 72275a8c820STejun Heo * offline (BPF scheduler bypassed), the task is migration-disabled, or a 72375a8c820STejun Heo * migration is pending. Admit despite the missing caps and count it. 7246ea3be36STejun Heo * Refuse preemptions. 72575a8c820STejun Heo */ 72675a8c820STejun Heo if (unlikely(!scx_rq_online(rq) || is_migration_disabled(p) || 72775a8c820STejun Heo p->migration_pending)) { 72875a8c820STejun Heo __scx_add_event(sch, SCX_EV_SUB_FORCED_ADMIT, 1); 7296ea3be36STejun Heo *enq_flags &= ~SCX_ENQ_PREEMPT; 73075a8c820STejun Heo return &rq->scx.local_dsq; 73175a8c820STejun Heo } 73275a8c820STejun Heo 7335fd50174STejun Heo /* 7345fd50174STejun Heo * Diverting to rescue or reject, neither of which honors IMMED, PREEMPT 7355fd50174STejun Heo * or HEAD - a diversion has no priority and IMMED is not allowed on 7365fd50174STejun Heo * non-local DSQs. Strip the enq and task flags along with the slice. 7375fd50174STejun Heo */ 7385fd50174STejun Heo *enq_flags &= ~(SCX_ENQ_IMMED | SCX_ENQ_PREEMPT | SCX_ENQ_HEAD | 7395fd50174STejun Heo SCX_ENQ_APPLY_SLICE | SCX_ENQ_SLICE_DFL); 7405fd50174STejun Heo p->scx.flags &= ~SCX_TASK_IMMED; 7415fd50174STejun Heo 7425fd50174STejun Heo /* the enqueuer opted for rescue instead of rejection and reenqueue */ 7435fd50174STejun Heo if ((*enq_flags & SCX_ENQ_RESCUE) && likely(scx_rescue_bw_1024)) { 7445fd50174STejun Heo __scx_add_event(sch, SCX_EV_SUB_RESCUE, 1); 7455fd50174STejun Heo if (scx_rescue_try_admit(rq, p)) 7465fd50174STejun Heo return &rq->scx.local_dsq; 747*bb70e4fbSTejun Heo 748*bb70e4fbSTejun Heo /* queueing, the overload trigger measures the wait from here */ 749*bb70e4fbSTejun Heo p->scx.rescue_at = jiffies; 7505fd50174STejun Heo return &rq->scx.rescue.dsq; 7515fd50174STejun Heo } 7525fd50174STejun Heo 75375a8c820STejun Heo p->scx.reenq_reason_caps = missing; 75475a8c820STejun Heo p->scx.reenq_reason_cid = cid; 75575a8c820STejun Heo 75675a8c820STejun Heo return &rq->scx.reject_dsq; 75775a8c820STejun Heo } 75875a8c820STejun Heo 75975a8c820STejun Heo /* @p lost the caps needed to stay on @rq's local DSQ? Record reason if so. */ 76075a8c820STejun Heo bool scx_task_reenq_on_cap_revoke(struct rq *rq, struct task_struct *p) 76175a8c820STejun Heo { 76275a8c820STejun Heo u64 missing; 76375a8c820STejun Heo 7645fd50174STejun Heo /* migration-disabled tasks and the rescuee are admitted capless */ 7655fd50174STejun Heo if (is_migration_disabled(p) || p == scx_rescuee(rq)) 76675a8c820STejun Heo return false; 76775a8c820STejun Heo 76875a8c820STejun Heo missing = scx_missing_caps(scx_task_sched(p), cpu_of(rq), scx_caps_for_task(p)); 76975a8c820STejun Heo if (likely(!missing)) 77075a8c820STejun Heo return false; 77175a8c820STejun Heo 77275a8c820STejun Heo p->scx.reenq_reason_caps = missing; 77375a8c820STejun Heo p->scx.reenq_reason_cid = __scx_cpu_to_cid(cpu_of(rq)); 77475a8c820STejun Heo return true; 77575a8c820STejun Heo } 77675a8c820STejun Heo 77775a8c820STejun Heo /* 77875a8c820STejun Heo * Drain @rq->scx.reject_dsq, reenqueueing each task so the BPF re-decides 77975a8c820STejun Heo * from p->scx.reenq_reason_*. 78075a8c820STejun Heo * 7817706d6e4STejun Heo * A task can be re-rejected repeatedly. The reenqueue is bounded per task in 7827706d6e4STejun Heo * scx_do_enqueue_task(), which ejects the owning sub past SCX_REENQ_MAX_REPEAT. 7837706d6e4STejun Heo * Rejection can't happen for root. 78475a8c820STejun Heo */ 78575a8c820STejun Heo void scx_reenq_reject(struct rq *rq) 78675a8c820STejun Heo { 78775a8c820STejun Heo LIST_HEAD(tasks); 78875a8c820STejun Heo struct task_struct *p, *n; 78975a8c820STejun Heo 79075a8c820STejun Heo lockdep_assert_rq_held(rq); 79175a8c820STejun Heo 7928946dbd3STejun Heo if (!scx_has_subs() || list_empty(&rq->scx.reject_dsq.list)) 79375a8c820STejun Heo return; 79475a8c820STejun Heo 79575a8c820STejun Heo /* 79675a8c820STejun Heo * Move to a private list so a task re-rejected by the 79775a8c820STejun Heo * scx_do_enqueue_task() below isn't revisited this round. 79875a8c820STejun Heo */ 79975a8c820STejun Heo list_for_each_entry_safe(p, n, &rq->scx.reject_dsq.list, scx.dsq_list.node) { 80075a8c820STejun Heo /* migration_pending tasks should have bypassed to local DSQ */ 80175a8c820STejun Heo if (WARN_ON_ONCE(p->migration_pending)) 80275a8c820STejun Heo continue; 80375a8c820STejun Heo 80475a8c820STejun Heo scx_dispatch_dequeue(rq, p); 80575a8c820STejun Heo 80675a8c820STejun Heo if (WARN_ON_ONCE(p->scx.flags & SCX_TASK_REENQ_REASON_MASK)) 80775a8c820STejun Heo p->scx.flags &= ~SCX_TASK_REENQ_REASON_MASK; 80875a8c820STejun Heo p->scx.flags |= SCX_TASK_REENQ_CAP; 80975a8c820STejun Heo 81075a8c820STejun Heo list_add_tail(&p->scx.dsq_list.node, &tasks); 81175a8c820STejun Heo } 81275a8c820STejun Heo 81375a8c820STejun Heo list_for_each_entry_safe(p, n, &tasks, scx.dsq_list.node) { 81475a8c820STejun Heo list_del_init(&p->scx.dsq_list.node); 81575a8c820STejun Heo 81675a8c820STejun Heo scx_do_enqueue_task(rq, p, SCX_ENQ_REENQ, -1); 81775a8c820STejun Heo 81875a8c820STejun Heo p->scx.flags &= ~SCX_TASK_REENQ_REASON_MASK; 81975a8c820STejun Heo } 82075a8c820STejun Heo } 82175a8c820STejun Heo 8225f2a9a4cSTejun Heo /* record a caps change, see struct scx_caps_updated */ 8235f2a9a4cSTejun Heo static void caps_updated_record(struct scx_pshard *ps, const struct scx_cmask *cids, u64 caps, 8245f2a9a4cSTejun Heo struct list_head *to_deliver) 8255f2a9a4cSTejun Heo { 8265f2a9a4cSTejun Heo struct scx_caps_updated *cu = &ps->caps_updated; 8275f2a9a4cSTejun Heo 8285f2a9a4cSTejun Heo guard(raw_spinlock)(&cu->lock); 8295f2a9a4cSTejun Heo scx_cmask_or(&cu->cmask, cids); 8305f2a9a4cSTejun Heo cu->caps |= caps; 8315f2a9a4cSTejun Heo if (list_empty(&cu->node_in_flight)) 8325f2a9a4cSTejun Heo list_add_tail(&cu->node_in_flight, to_deliver); 8335f2a9a4cSTejun Heo } 8345f2a9a4cSTejun Heo 8355f2a9a4cSTejun Heo /* deliver queued caps_updated callbacks, see struct scx_caps_updated */ 8365f2a9a4cSTejun Heo static void caps_updated_deliver(struct list_head *to_deliver) 8375f2a9a4cSTejun Heo { 8385f2a9a4cSTejun Heo struct scx_caps_updated *cu, *tmp; 8395f2a9a4cSTejun Heo 8405f2a9a4cSTejun Heo list_for_each_entry_safe(cu, tmp, to_deliver, node_in_flight) { 8415f2a9a4cSTejun Heo struct scx_pshard *ps = container_of(cu, struct scx_pshard, caps_updated); 8425f2a9a4cSTejun Heo struct scx_sched *sch = ps->sch; 8435f2a9a4cSTejun Heo 8445f2a9a4cSTejun Heo while (true) { 8455f2a9a4cSTejun Heo u64 caps = 0; 8465f2a9a4cSTejun Heo 8475f2a9a4cSTejun Heo /* 8485f2a9a4cSTejun Heo * During enable, has_op is set after ops.sub_attach(), 8495f2a9a4cSTejun Heo * so !has_op means the op is absent or the sched isn't 8505f2a9a4cSTejun Heo * live yet - e.g. caps grant from ops.sub_attach(). 8515f2a9a4cSTejun Heo * Either way don't consume - leave for 8525f2a9a4cSTejun Heo * scx_sub_seed_caps() to deliver once live. 8535f2a9a4cSTejun Heo */ 8545f2a9a4cSTejun Heo scoped_guard (raw_spinlock, &cu->lock) { 8555f2a9a4cSTejun Heo if (cu->caps && SCX_HAS_OP(sch, sub_caps_updated) && 8565f2a9a4cSTejun Heo likely(!READ_ONCE(sch->aborting))) { 8575f2a9a4cSTejun Heo struct scx_cmask_ref ref; 8585f2a9a4cSTejun Heo 8595f2a9a4cSTejun Heo caps = cu->caps; 8605f2a9a4cSTejun Heo scx_cmask_ref_init_kern(sch, cu->cmask_arena_out, 8615f2a9a4cSTejun Heo ps->base, ps->nr_cids, &ref); 8625f2a9a4cSTejun Heo scx_cmask_ref_copy(&ref, &cu->cmask); 8635f2a9a4cSTejun Heo scx_cmask_clear(&cu->cmask); 8645f2a9a4cSTejun Heo cu->caps = 0; 8655f2a9a4cSTejun Heo } else { 8665f2a9a4cSTejun Heo list_del_init(&cu->node_in_flight); 8675f2a9a4cSTejun Heo } 8685f2a9a4cSTejun Heo } 8695f2a9a4cSTejun Heo if (!caps) 8705f2a9a4cSTejun Heo break; 8715f2a9a4cSTejun Heo 8725f2a9a4cSTejun Heo /* caps != 0 only when deliverable (has_op, above) */ 8735f2a9a4cSTejun Heo SCX_CALL_OP(sch, sub_caps_updated, NULL, 8745f2a9a4cSTejun Heo scx_kaddr_to_arena(sch, cu->cmask_arena_out), 8755f2a9a4cSTejun Heo caps); 8765f2a9a4cSTejun Heo } 8775f2a9a4cSTejun Heo } 8785f2a9a4cSTejun Heo } 8795f2a9a4cSTejun Heo 8805f2a9a4cSTejun Heo /* 8815f2a9a4cSTejun Heo * Deliver caps owed to @sch that couldn't be delivered earlier (e.g. a grant 8825f2a9a4cSTejun Heo * taken during its sub_attach(), before has_op was set). Called once @sch is 8835f2a9a4cSTejun Heo * enabled. 8845f2a9a4cSTejun Heo */ 8855f2a9a4cSTejun Heo static void scx_sub_seed_caps(struct scx_sched *sch) 8865f2a9a4cSTejun Heo { 8875f2a9a4cSTejun Heo LIST_HEAD(to_deliver); 8885f2a9a4cSTejun Heo s32 si; 8895f2a9a4cSTejun Heo 8905f2a9a4cSTejun Heo guard(irqsave)(); 8915f2a9a4cSTejun Heo 8925f2a9a4cSTejun Heo for (si = 0; si < sch->nr_pshards; si++) { 8935f2a9a4cSTejun Heo struct scx_pshard *ps = sch->pshard[si]; 8945f2a9a4cSTejun Heo struct scx_caps_updated *cu = &ps->caps_updated; 8955f2a9a4cSTejun Heo 8965f2a9a4cSTejun Heo scoped_guard (raw_spinlock, &cu->lock) { 8975f2a9a4cSTejun Heo if (cu->caps && list_empty(&cu->node_in_flight)) 8985f2a9a4cSTejun Heo list_add_tail(&cu->node_in_flight, &to_deliver); 8995f2a9a4cSTejun Heo } 9005f2a9a4cSTejun Heo } 9015f2a9a4cSTejun Heo caps_updated_deliver(&to_deliver); 9025f2a9a4cSTejun Heo } 9035f2a9a4cSTejun Heo 90456fdc35bSTejun Heo static u64 calc_effective_caps(struct scx_pshard *ps, s32 cid) 90556fdc35bSTejun Heo { 90656fdc35bSTejun Heo u64 ecaps = 0; 90756fdc35bSTejun Heo u32 cap_bit; 90856fdc35bSTejun Heo 90956fdc35bSTejun Heo for (cap_bit = 0; cap_bit < __SCX_NR_CAPS; cap_bit++) 91056fdc35bSTejun Heo if (scx_cmask_test(cid, &ps->caps[cap_bit].cmask)) 91156fdc35bSTejun Heo ecaps |= BIT_U64(cap_bit) | scx_caps_implied(BIT_U64(cap_bit)); 91256fdc35bSTejun Heo return ecaps; 91356fdc35bSTejun Heo } 91456fdc35bSTejun Heo 91556fdc35bSTejun Heo /** 91656fdc35bSTejun Heo * queue_sync_ecaps - Queue ecaps update for a (sch, cid) pair 91756fdc35bSTejun Heo * @sch: sched to update 91856fdc35bSTejun Heo * @cid: cid to update 91956fdc35bSTejun Heo * 92056fdc35bSTejun Heo * Queue an ecaps update for @sch's @cid and kick the cpu so that it syncs in 92156fdc35bSTejun Heo * balance_one(). 92256fdc35bSTejun Heo */ 92356fdc35bSTejun Heo static void queue_sync_ecaps(struct scx_sched *sch, s32 cid) 92456fdc35bSTejun Heo { 92556fdc35bSTejun Heo s32 cpu = __scx_cid_to_cpu(cid); 92656fdc35bSTejun Heo struct scx_sched_pcpu *pcpu = per_cpu_ptr(sch->pcpu, cpu); 92756fdc35bSTejun Heo 92856fdc35bSTejun Heo /* 92956fdc35bSTejun Heo * Pairs with smp_mb() in scx_process_sync_ecaps(). Either the check 93056fdc35bSTejun Heo * below sees the node off the list and queues it, or the in-flight sync 93156fdc35bSTejun Heo * sees the caps[] update made before this call. 93256fdc35bSTejun Heo */ 93356fdc35bSTejun Heo smp_mb(); 93456fdc35bSTejun Heo 93556fdc35bSTejun Heo /* @cid's pshard->lock excludes concurrent queueing attempts */ 93656fdc35bSTejun Heo if (llist_on_list(&pcpu->ecaps_to_sync_node)) 93756fdc35bSTejun Heo return; 93856fdc35bSTejun Heo if (llist_add(&pcpu->ecaps_to_sync_node, &cpu_rq(cpu)->scx.ecaps_to_sync)) 939ce228343STejun Heo scx_kick_cpu(sch->ancestors[0], cpu, 0); 94056fdc35bSTejun Heo } 94156fdc35bSTejun Heo 94256fdc35bSTejun Heo /* discard @rq's queued ecaps syncs */ 94356fdc35bSTejun Heo static void discard_queued_syncs(struct rq *rq) 94456fdc35bSTejun Heo { 94556fdc35bSTejun Heo struct llist_node *pos, *tmp; 94656fdc35bSTejun Heo 94756fdc35bSTejun Heo lockdep_assert_rq_held(rq); 94856fdc35bSTejun Heo 94956fdc35bSTejun Heo llist_for_each_safe(pos, tmp, llist_del_all(&rq->scx.ecaps_to_sync)) 95056fdc35bSTejun Heo init_llist_node(pos); 95156fdc35bSTejun Heo } 95256fdc35bSTejun Heo 95356fdc35bSTejun Heo /** 95456fdc35bSTejun Heo * scx_process_sync_ecaps - Sync this cpu's ecaps to pshard->caps[] 95556fdc35bSTejun Heo * @rq: the cid's cpu rq 956b81a6c01STejun Heo * @prev: @rq's previous task from the in-progress balance 95756fdc35bSTejun Heo * 95856fdc35bSTejun Heo * pshard->caps[] is the target configuration. pcpu->ecaps is the effective 95956fdc35bSTejun Heo * transposed copy owned by the cid's cpu and written only here under @rq's 96056fdc35bSTejun Heo * lock. 961ca3aec45STejun Heo * 962ca3aec45STejun Heo * A sched that newly gains baseline access here is owed an update_idle() so it 963ca3aec45STejun Heo * learns the cid's idle state. Such a gain arms the per-rq 964ca3aec45STejun Heo * %SCX_RQ_SUB_IDLE_RENOTIFY gate so the next idle pick delivers it. 96556fdc35bSTejun Heo */ 966b81a6c01STejun Heo void scx_process_sync_ecaps(struct rq *rq, struct task_struct *prev) 96756fdc35bSTejun Heo { 968b81a6c01STejun Heo s32 cpu = cpu_of(rq); 969b81a6c01STejun Heo s32 cid, shard; 97056fdc35bSTejun Heo struct llist_node *batch, *pos, *tmp; 97175a8c820STejun Heo u64 lost_all = 0; 97256fdc35bSTejun Heo 97356fdc35bSTejun Heo lockdep_assert_rq_held(rq); 97456fdc35bSTejun Heo 9758946dbd3STejun Heo if (!scx_has_subs() || likely(llist_empty(&rq->scx.ecaps_to_sync))) 97656fdc35bSTejun Heo return; 97756fdc35bSTejun Heo 978b81a6c01STejun Heo /* 979b81a6c01STejun Heo * ecaps are zeroed while the cpu is inactive and must stay zero. 980b81a6c01STejun Heo * Discard queued syncs instead of processing them - the 981b81a6c01STejun Heo * scx_online_ecaps() reseed re-syncs every sched on activation. 982b81a6c01STejun Heo * cpu_active() clears before the offline zeroing and sets before the 983b81a6c01STejun Heo * reseed is queued, so this test can neither miss a racing sync nor 984b81a6c01STejun Heo * eat the reseed. 985b81a6c01STejun Heo */ 986b81a6c01STejun Heo if (unlikely(!cpu_active(cpu))) { 987b81a6c01STejun Heo discard_queued_syncs(rq); 988b81a6c01STejun Heo return; 989b81a6c01STejun Heo } 990b81a6c01STejun Heo 991b81a6c01STejun Heo /* @cid is valid here: the cpu is active with queued syncs */ 992b81a6c01STejun Heo cid = __scx_cpu_to_cid(cpu); 9933a773220STejun Heo shard = rcu_dereference_all(scx_cid_to_shard)[cid]; 994b81a6c01STejun Heo 99556fdc35bSTejun Heo batch = llist_del_all(&rq->scx.ecaps_to_sync); 99656fdc35bSTejun Heo llist_for_each_safe(pos, tmp, batch) { 99756fdc35bSTejun Heo struct scx_sched_pcpu *pcpu = 99856fdc35bSTejun Heo container_of(pos, struct scx_sched_pcpu, ecaps_to_sync_node); 99956fdc35bSTejun Heo struct scx_pshard *ps = pcpu->sch->pshard[shard]; 1000ca3aec45STejun Heo u64 old, ecaps, lost, gained; 100156fdc35bSTejun Heo 100256fdc35bSTejun Heo init_llist_node(pos); 100356fdc35bSTejun Heo 100456fdc35bSTejun Heo /* pairs with smp_mb() in queue_sync_ecaps(), see there */ 100556fdc35bSTejun Heo smp_mb(); 100656fdc35bSTejun Heo 100775a8c820STejun Heo old = READ_ONCE(pcpu->ecaps); 1008b81a6c01STejun Heo ecaps = calc_effective_caps(ps, cid); 1009b81a6c01STejun Heo WRITE_ONCE(pcpu->ecaps, ecaps); 1010b81a6c01STejun Heo 101175a8c820STejun Heo lost = old & ~ecaps; 1012ca3aec45STejun Heo gained = ecaps & ~old; 101375a8c820STejun Heo lost_all |= lost; 101475a8c820STejun Heo 101534e0fbfeSTejun Heo /* 101634e0fbfeSTejun Heo * Tell the sched its effective caps on this cid changed. The 101734e0fbfeSTejun Heo * invocation is equivalent to the dispatch path and may drop 101834e0fbfeSTejun Heo * and re-acquire the rq lock temporarily while the rest of 101934e0fbfeSTejun Heo * @batch is held privately, see scx_discard_ecaps_to_sync(). 102034e0fbfeSTejun Heo */ 1021b81a6c01STejun Heo if (ecaps != pcpu->reported_ecaps && 1022b81a6c01STejun Heo SCX_HAS_OP(pcpu->sch, sub_ecaps_updated) && 1023b81a6c01STejun Heo !scx_bypassing(pcpu->sch, cpu)) { 1024b81a6c01STejun Heo struct scx_dsp_ctx *dspc = &pcpu->dsp_ctx; 1025b81a6c01STejun Heo 1026b81a6c01STejun Heo dspc->rq = rq; 1027b81a6c01STejun Heo /* stash @prev so nested dispatches can access it */ 1028b81a6c01STejun Heo rq->scx.sub_dispatch_prev = prev; 1029b81a6c01STejun Heo SCX_CALL_OP(pcpu->sch, sub_ecaps_updated, rq, scx_cpu_arg(cpu), 1030b81a6c01STejun Heo pcpu->reported_ecaps, ecaps); 1031b81a6c01STejun Heo rq->scx.sub_dispatch_prev = NULL; 1032b81a6c01STejun Heo scx_flush_dispatch_buf(pcpu->sch, rq); 1033b81a6c01STejun Heo pcpu->reported_ecaps = ecaps; 1034b81a6c01STejun Heo } 1035ca3aec45STejun Heo 1036ca3aec45STejun Heo /* 1037ca3aec45STejun Heo * Gaining baseline access owes an update_idle() so the sched 1038ca3aec45STejun Heo * learns the cpu's idle state. Arm the per-rq gate so the next 1039ca3aec45STejun Heo * idle pick flushes it. Losing access drops any pending notify. 1040ca3aec45STejun Heo */ 1041ca3aec45STejun Heo if (gained & SCX_CAP_BASE) { 1042ca3aec45STejun Heo pcpu->idle_renotify = true; 1043ca3aec45STejun Heo rq->scx.flags |= SCX_RQ_SUB_IDLE_RENOTIFY; 1044ca3aec45STejun Heo } else if (lost & SCX_CAP_BASE) { 1045ca3aec45STejun Heo pcpu->idle_renotify = false; 1046ca3aec45STejun Heo } 1047b81a6c01STejun Heo } 104875a8c820STejun Heo 104975a8c820STejun Heo /* 105075a8c820STejun Heo * Losing a cap can strand already-queued tasks. Schedule a reenq scan 105175a8c820STejun Heo * to move the now-capless ones off the local DSQ. The scan tests 105275a8c820STejun Heo * against the effective caps and thus must come after the ecaps sync. 105375a8c820STejun Heo */ 105475a8c820STejun Heo if (lost_all & SCX_CAPS_REENQ_ON_LOSS) 105575a8c820STejun Heo scx_schedule_reenq_local(rq, SCX_REENQ_CAP_REVOKE); 1056b81a6c01STejun Heo } 1057b81a6c01STejun Heo 105875c268edSTejun Heo /** 105975c268edSTejun Heo * scx_unbypass_replay_ecaps - Replay a bypass-suppressed ecaps notification 106075c268edSTejun Heo * @rq: rq of the cpu leaving bypass 106175c268edSTejun Heo * @sch: scheduler that just left bypass on @rq's cpu 106275c268edSTejun Heo * 106375c268edSTejun Heo * scx_process_sync_ecaps() consumes syncs while bypassing without delivering 106475c268edSTejun Heo * ops.sub_ecaps_updated(), leaving reported_ecaps stale. Nothing re-queues a 106575c268edSTejun Heo * sync when bypass lifts, so without a replay a cid that never changes again 106675c268edSTejun Heo * would never be notified. The attach-time initial grants are the acute case 106775c268edSTejun Heo * as they are consumed during the enable bypass window. Re-queue a sync for 106875c268edSTejun Heo * any undelivered delta so the next balance delivers it. 106975c268edSTejun Heo */ 107075c268edSTejun Heo void scx_unbypass_replay_ecaps(struct rq *rq, struct scx_sched *sch) 107175c268edSTejun Heo { 107275c268edSTejun Heo s32 cpu = cpu_of(rq); 107375c268edSTejun Heo struct scx_sched_pcpu *pcpu = per_cpu_ptr(sch->pcpu, cpu); 107475c268edSTejun Heo struct scx_pshard *ps; 107575c268edSTejun Heo s32 cid; 107675c268edSTejun Heo 107775c268edSTejun Heo lockdep_assert_rq_held(rq); 107875c268edSTejun Heo 107975c268edSTejun Heo /* root holds every cap and never uses ecaps */ 108075c268edSTejun Heo if (!sch->level) 108175c268edSTejun Heo return; 108275c268edSTejun Heo 108375c268edSTejun Heo if (READ_ONCE(pcpu->ecaps) == pcpu->reported_ecaps) 108475c268edSTejun Heo return; 108575c268edSTejun Heo 108675c268edSTejun Heo cid = __scx_cpu_to_cid(cpu); 10873a773220STejun Heo ps = sch->pshard[rcu_dereference_all(scx_cid_to_shard)[cid]]; 108875c268edSTejun Heo 108975c268edSTejun Heo guard(raw_spinlock)(&ps->lock); 109075c268edSTejun Heo queue_sync_ecaps(sch, cid); 109175c268edSTejun Heo } 109275c268edSTejun Heo 1093b81a6c01STejun Heo /* 1094b81a6c01STejun Heo * A cpu came back. Re-seed each sub-sched's ecaps on the cpu's cid. The sync 1095b81a6c01STejun Heo * recomputes effective caps from the pshard and fires ops.sub_ecaps_updated() 1096b81a6c01STejun Heo * only on a real change since offline. 1097b81a6c01STejun Heo */ 1098b81a6c01STejun Heo void scx_online_ecaps(struct rq *rq) 1099b81a6c01STejun Heo { 1100ce228343STejun Heo struct scx_sched *root, *pos; 11013a773220STejun Heo s32 cid, shard; 11023a773220STejun Heo 11033a773220STejun Heo /* 11043a773220STejun Heo * Only a live hierarchy can have ecaps to reseed. This also keeps the 11053a773220STejun Heo * table reads below away from an enable that failed before publishing 11063a773220STejun Heo * the tables. A concurrent disable can't retire them, see 11073a773220STejun Heo * handle_hotplug(). 11083a773220STejun Heo */ 11093a773220STejun Heo if (!scx_enabled()) 11103a773220STejun Heo return; 1111b81a6c01STejun Heo 1112b81a6c01STejun Heo guard(rq_lock_irqsave)(rq); 1113b81a6c01STejun Heo 1114ce228343STejun Heo root = scx_root_protected(); 11153a773220STejun Heo cid = __scx_cpu_to_cid(cpu_of(rq)); 11163a773220STejun Heo shard = rcu_dereference_all(scx_cid_to_shard)[cid]; 11173a773220STejun Heo 1118ce228343STejun Heo scx_for_each_descendant_pre(pos, root) { 1119b81a6c01STejun Heo struct scx_pshard *ps; 1120b81a6c01STejun Heo 1121b81a6c01STejun Heo /* root holds every cap and never uses ecaps */ 1122ce228343STejun Heo if (!pos->level) 1123b81a6c01STejun Heo continue; 1124b81a6c01STejun Heo 1125b81a6c01STejun Heo ps = pos->pshard[shard]; 1126b81a6c01STejun Heo guard(raw_spinlock)(&ps->lock); 1127b81a6c01STejun Heo queue_sync_ecaps(pos, cid); 1128b81a6c01STejun Heo } 1129b81a6c01STejun Heo } 1130b81a6c01STejun Heo 1131b81a6c01STejun Heo /* 1132b81a6c01STejun Heo * A cpu is going down. Zero each sub-sched's in-effect ecaps so cap checks 1133b81a6c01STejun Heo * treat the cpu as capless while offline. Pending and late-queued syncs are 1134b81a6c01STejun Heo * discarded at consumption by scx_process_sync_ecaps() while the cpu is 1135b81a6c01STejun Heo * inactive. Leave reported_ecaps. Ownership is unchanged, so the 1136b81a6c01STejun Heo * scx_online_ecaps() reseed reports only a genuine delta. No callback fires 1137b81a6c01STejun Heo * here. 1138b81a6c01STejun Heo */ 1139b81a6c01STejun Heo void scx_offline_ecaps(struct rq *rq) 1140b81a6c01STejun Heo { 1141b81a6c01STejun Heo s32 cpu = cpu_of(rq); 1142ce228343STejun Heo struct scx_sched *root, *pos; 1143b81a6c01STejun Heo 1144b81a6c01STejun Heo guard(rq_lock_irqsave)(rq); 1145b81a6c01STejun Heo 1146ce228343STejun Heo root = scx_root_protected(); 1147ce228343STejun Heo 1148ce228343STejun Heo scx_for_each_descendant_pre(pos, root) { 1149b81a6c01STejun Heo /* root holds every cap and never uses ecaps */ 1150ce228343STejun Heo if (!pos->level) 1151b81a6c01STejun Heo continue; 1152b81a6c01STejun Heo 1153b81a6c01STejun Heo WRITE_ONCE(per_cpu_ptr(pos->pcpu, cpu)->ecaps, 0); 115456fdc35bSTejun Heo } 115556fdc35bSTejun Heo } 115656fdc35bSTejun Heo 115756fdc35bSTejun Heo /* 115834e0fbfeSTejun Heo * @pcpu's sched was unhashed before the grace period, so nothing re-queues its 115934e0fbfeSTejun Heo * sync node. Remove the node from @rq's pending list so the pcpu can be freed. 116056fdc35bSTejun Heo */ 116156fdc35bSTejun Heo void scx_discard_ecaps_to_sync(s32 cpu, struct scx_sched_pcpu *pcpu) 116256fdc35bSTejun Heo { 1163b81a6c01STejun Heo struct rq *rq = cpu_rq(cpu); 116434e0fbfeSTejun Heo struct llist_node *head = NULL, *tail = NULL; 116534e0fbfeSTejun Heo struct llist_node *pos, *tmp; 116656fdc35bSTejun Heo 116734e0fbfeSTejun Heo /* 116834e0fbfeSTejun Heo * llist can't unlink a single node. Take all queued nodes, drop @pcpu's 116934e0fbfeSTejun Heo * and resplice the rest. Nodes in the taken batch read as on-list 117034e0fbfeSTejun Heo * throughout, so queue_sync_ecaps() stays correct. 117134e0fbfeSTejun Heo */ 117234e0fbfeSTejun Heo if (llist_on_list(&pcpu->ecaps_to_sync_node)) { 117334e0fbfeSTejun Heo scoped_guard (rq_lock_irqsave, rq) { 117434e0fbfeSTejun Heo llist_for_each_safe(pos, tmp, llist_del_all(&rq->scx.ecaps_to_sync)) { 117534e0fbfeSTejun Heo if (pos == &pcpu->ecaps_to_sync_node) { 117634e0fbfeSTejun Heo init_llist_node(pos); 117734e0fbfeSTejun Heo } else { 117834e0fbfeSTejun Heo pos->next = head; 117934e0fbfeSTejun Heo head = pos; 118034e0fbfeSTejun Heo if (!tail) 118134e0fbfeSTejun Heo tail = pos; 118234e0fbfeSTejun Heo } 118334e0fbfeSTejun Heo } 118434e0fbfeSTejun Heo if (head) 118534e0fbfeSTejun Heo llist_add_batch(head, tail, &rq->scx.ecaps_to_sync); 118634e0fbfeSTejun Heo } 118734e0fbfeSTejun Heo } 118834e0fbfeSTejun Heo 118934e0fbfeSTejun Heo /* 119034e0fbfeSTejun Heo * An in-flight scx_process_sync_ecaps() batch may still hold the node 119134e0fbfeSTejun Heo * privately across dispatch-induced rq unlocks, reading as on-list. 119234e0fbfeSTejun Heo * 119334e0fbfeSTejun Heo * Because a bypassing sched gets no op call, init_llist_node() and all 119434e0fbfeSTejun Heo * @pcpu accesses share one contiguous lock hold, off-list under the rq 119534e0fbfeSTejun Heo * lock means @pcpu won't be accessed again. 119634e0fbfeSTejun Heo */ 1197b81a6c01STejun Heo while (true) { 1198b81a6c01STejun Heo scoped_guard (rq_lock_irqsave, rq) { 1199b81a6c01STejun Heo if (!llist_on_list(&pcpu->ecaps_to_sync_node)) 1200b81a6c01STejun Heo return; 1201b81a6c01STejun Heo } 120234e0fbfeSTejun Heo cpu_relax(); 1203b81a6c01STejun Heo } 120456fdc35bSTejun Heo } 120556fdc35bSTejun Heo 120656fdc35bSTejun Heo /** 120756fdc35bSTejun Heo * scx_discard_stale_ecaps_syncs - Discard ecaps syncs from earlier schedulers 120856fdc35bSTejun Heo * 120956fdc35bSTejun Heo * To be called during root enable before the scheduler goes live. An earlier 121056fdc35bSTejun Heo * root's sub-sched may not have gone through its RCU free path yet (e.g. a 121156fdc35bSTejun Heo * still-open link fd defers it) and can leave queued ecaps syncs behind. 121256fdc35bSTejun Heo * Processing them would decode the dead sched's pshards with the current cid 121356fdc35bSTejun Heo * layout. Discard them instead. The backing scx_sched_pcpu's are still 121434e0fbfeSTejun Heo * allocated as the free path removes ecaps_to_sync_node before freeing. 121556fdc35bSTejun Heo */ 121656fdc35bSTejun Heo void scx_discard_stale_ecaps_syncs(void) 121756fdc35bSTejun Heo { 121856fdc35bSTejun Heo s32 cpu; 121956fdc35bSTejun Heo 122056fdc35bSTejun Heo for_each_possible_cpu(cpu) { 122156fdc35bSTejun Heo struct rq *rq = cpu_rq(cpu); 122256fdc35bSTejun Heo 122356fdc35bSTejun Heo guard(rq_lock_irqsave)(rq); 122456fdc35bSTejun Heo discard_queued_syncs(rq); 122556fdc35bSTejun Heo } 122656fdc35bSTejun Heo } 122756fdc35bSTejun Heo 1228daf8e166STejun Heo static DECLARE_WAIT_QUEUE_HEAD(scx_unlink_waitq); 1229daf8e166STejun Heo 1230daf8e166STejun Heo void drain_descendants(struct scx_sched *sch) 1231daf8e166STejun Heo { 1232daf8e166STejun Heo /* 1233daf8e166STejun Heo * Child scheds that finished the critical part of disabling will take 1234daf8e166STejun Heo * themselves off @sch->children. Wait for it to drain. As propagation 1235daf8e166STejun Heo * is recursive, empty @sch->children means that all proper descendant 1236daf8e166STejun Heo * scheds reached unlinking stage. 1237daf8e166STejun Heo */ 1238daf8e166STejun Heo wait_event(scx_unlink_waitq, list_empty(&sch->children)); 1239daf8e166STejun Heo } 1240daf8e166STejun Heo 12410dc90ce1STejun Heo /** 12420dc90ce1STejun Heo * scx_rehome_task - Move a task to a sched it has been initialized for 12430dc90ce1STejun Heo * @to: sched taking over @p, @p's init on it already complete 12440dc90ce1STejun Heo * @p: task to re-home 12450dc90ce1STejun Heo * 12460dc90ce1STejun Heo * Exit @p from its current sched and switch it over to @to, overriding the 12470dc90ce1STejun Heo * state to %SCX_TASK_READY to account for the already completed init. A task 12480dc90ce1STejun Heo * on a non-ext class, possible under an %SCX_OPS_SWITCH_PARTIAL root, stays 12490dc90ce1STejun Heo * %READY and is enabled by switching_to_scx() if it switches over. 12500dc90ce1STejun Heo */ 12510dc90ce1STejun Heo static void scx_rehome_task(struct scx_sched *to, struct task_struct *p) 12520dc90ce1STejun Heo { 12530dc90ce1STejun Heo lockdep_assert_held(&p->pi_lock); 12540dc90ce1STejun Heo lockdep_assert_rq_held(task_rq(p)); 12550dc90ce1STejun Heo 12560dc90ce1STejun Heo scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE) { 12570dc90ce1STejun Heo scx_disable_and_exit_task(scx_task_sched(p), p); 12580dc90ce1STejun Heo scx_set_task_state(p, SCX_TASK_INIT_BEGIN); 12590dc90ce1STejun Heo scx_set_task_state(p, SCX_TASK_INIT); 12600dc90ce1STejun Heo scx_set_task_sched(p, to); 12610dc90ce1STejun Heo scx_set_task_state(p, SCX_TASK_READY); 12620dc90ce1STejun Heo if (p->sched_class == &ext_sched_class) 12630dc90ce1STejun Heo scx_enable_task(to, p); 12640dc90ce1STejun Heo } 12650dc90ce1STejun Heo } 12660dc90ce1STejun Heo 12670dc90ce1STejun Heo /** 12680dc90ce1STejun Heo * scx_punt_task - Hand a task to a failed sched without initialization 12690dc90ce1STejun Heo * @to: failed and bypassed sched taking custody of @p 12700dc90ce1STejun Heo * @p: task to punt 12710dc90ce1STejun Heo * 12720dc90ce1STejun Heo * Take @p off its current sched and put it on @to at %SCX_TASK_NONE. @to is 12730dc90ce1STejun Heo * dying and its teardown will re-home @p properly. 12740dc90ce1STejun Heo * 12750dc90ce1STejun Heo * Used when @to must take over @p but failed to initialize it. Bypass keeps 12760dc90ce1STejun Heo * scheduling decisions away from @to but @p can still trigger its task ops, 12770dc90ce1STejun Heo * which may confuse the BPF side. @to is dying anyway. The exit paths skip 12780dc90ce1STejun Heo * %NONE tasks (see __scx_disable_and_exit_task() and switched_from_scx()). 12790dc90ce1STejun Heo */ 12800dc90ce1STejun Heo static void scx_punt_task(struct scx_sched *to, struct task_struct *p) 12810dc90ce1STejun Heo { 12820dc90ce1STejun Heo lockdep_assert_held(&p->pi_lock); 12830dc90ce1STejun Heo lockdep_assert_rq_held(task_rq(p)); 12840dc90ce1STejun Heo WARN_ON_ONCE(!READ_ONCE(to->bypass_depth)); 12850dc90ce1STejun Heo 12860dc90ce1STejun Heo scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE) { 12870dc90ce1STejun Heo scx_disable_and_exit_task(scx_task_sched(p), p); 12880dc90ce1STejun Heo scx_set_task_sched(p, to); 12890dc90ce1STejun Heo } 12900dc90ce1STejun Heo } 12910dc90ce1STejun Heo 1292daf8e166STejun Heo static void scx_fail_parent(struct scx_sched *sch, 1293daf8e166STejun Heo struct task_struct *failed, s32 fail_code) 1294daf8e166STejun Heo { 1295daf8e166STejun Heo struct scx_sched *parent = scx_parent(sch); 1296daf8e166STejun Heo struct scx_task_iter sti; 1297daf8e166STejun Heo struct task_struct *p; 1298daf8e166STejun Heo 1299daf8e166STejun Heo scx_error(parent, "ops.init_task() failed (%d) for %s[%d] while disabling a sub-scheduler", 1300daf8e166STejun Heo fail_code, failed->comm, failed->pid); 1301daf8e166STejun Heo 1302daf8e166STejun Heo /* 13030dc90ce1STejun Heo * Once $parent is bypassed, tasks can be punted into it. This may 13040dc90ce1STejun Heo * cause downstream failures on the BPF side but $parent is dying 13050dc90ce1STejun Heo * anyway. 1306daf8e166STejun Heo */ 1307daf8e166STejun Heo scx_bypass(parent, true); 1308daf8e166STejun Heo 1309daf8e166STejun Heo scx_task_iter_start(&sti, sch->cgrp); 1310daf8e166STejun Heo while ((p = scx_task_iter_next_locked(&sti))) { 1311daf8e166STejun Heo if (scx_task_on_sched(parent, p)) 1312daf8e166STejun Heo continue; 1313daf8e166STejun Heo 13140dc90ce1STejun Heo scx_punt_task(parent, p); 1315daf8e166STejun Heo } 1316daf8e166STejun Heo scx_task_iter_stop(&sti); 1317daf8e166STejun Heo } 1318daf8e166STejun Heo 1319a6ec0b62STejun Heo #ifdef CONFIG_EXT_GROUP_SCHED 1320a6ec0b62STejun Heo /** 1321a6ec0b62STejun Heo * scx_cgroup_claim_subtree - Claim the subtree's cgroups for an enabling sub 1322a6ec0b62STejun Heo * @sch: sub-scheduler being enabled 1323a6ec0b62STejun Heo * 1324a6ec0b62STejun Heo * Called while enabling @sch, after the subtree's cgrp->scx_sched's are pointed 1325a6ec0b62STejun Heo * at @sch and before any task is claimed. This mirrors root enable's 1326a6ec0b62STejun Heo * cgroups-before-tasks order. The ops.init_task() args are task_group-granular 1327a6ec0b62STejun Heo * and can still reference a cgroup outside the handed-over set when the cpu 1328a6ec0b62STejun Heo * controller is coarser than the sub topology or mounted on cgroup1. 1329a6ec0b62STejun Heo * 1330a6ec0b62STejun Heo * First init each of the parent sched's subtree cgroups on @sch, and only then 1331a6ec0b62STejun Heo * exit them from the parent, so that a failed init can be unwound with the 1332a6ec0b62STejun Heo * parent untouched. The both-inited transient is invisible outside 1333a6ec0b62STejun Heo * scx_cgroup_lock(). %SCX_TG_SUB_INIT tracks the first pass's progress. 1334a6ec0b62STejun Heo * %SCX_TG_INITED stays set throughout, except for a task_group whose 1335a6ec0b62STejun Heo * ops.cgroup_init() failed on the parent (see scx_cgroup_return_subtree()): 1336a6ec0b62STejun Heo * there is nothing to exit from the parent and %SCX_TG_INITED is set back with 1337a6ec0b62STejun Heo * the transfer. 1338a6ec0b62STejun Heo * 1339a6ec0b62STejun Heo * Dying but not yet offlined task_groups are included: a removed cgroup keeps 1340a6ec0b62STejun Heo * hosting scheduling events until its dying tasks finish their final context 1341a6ec0b62STejun Heo * switches, so it still needs to be inited on a sched, and its offline-time 1342a6ec0b62STejun Heo * ops.cgroup_exit() follows the last of those events. 1343a6ec0b62STejun Heo * 1344a6ec0b62STejun Heo * Return 0 on success, -errno on failure. On failure, @sch has been 1345a6ec0b62STejun Heo * scx_error()'d and is left with no cgroups. 1346a6ec0b62STejun Heo */ 1347a6ec0b62STejun Heo static s32 scx_cgroup_claim_subtree(struct scx_sched *sch) 1348a6ec0b62STejun Heo { 1349a6ec0b62STejun Heo struct cgroup *sub_cgrp = sch_cgroup(sch); 1350a6ec0b62STejun Heo struct cgroup_subsys_state *ecss = cgroup_e_css(sub_cgrp, &cpu_cgrp_subsys); 1351a6ec0b62STejun Heo struct scx_sched *parent = scx_parent(sch); 1352a6ec0b62STejun Heo struct cgroup_subsys_state *css; 1353a6ec0b62STejun Heo int ret; 1354a6ec0b62STejun Heo 1355a6ec0b62STejun Heo css_for_each_descendant_pre(css, ecss) { 1356a6ec0b62STejun Heo struct task_group *tg = css_tg(css); 1357a6ec0b62STejun Heo struct scx_cgroup_init_args args = { 1358a6ec0b62STejun Heo .weight = tg->scx.weight, 1359a6ec0b62STejun Heo .bw_period_us = tg->scx.bw_period_us, 1360a6ec0b62STejun Heo .bw_quota_us = tg->scx.bw_quota_us, 1361a6ec0b62STejun Heo .bw_burst_us = tg->scx.bw_burst_us, 1362a6ec0b62STejun Heo }; 1363a6ec0b62STejun Heo 1364a6ec0b62STejun Heo if (tg->scx.sched != parent || 1365a6ec0b62STejun Heo !cgroup_is_descendant(css->cgroup, sub_cgrp)) 1366a6ec0b62STejun Heo continue; 1367a6ec0b62STejun Heo 1368a6ec0b62STejun Heo if (SCX_HAS_OP(sch, cgroup_init)) { 1369a6ec0b62STejun Heo ret = SCX_CALL_OP_RET(sch, cgroup_init, NULL, css->cgroup, &args); 1370a6ec0b62STejun Heo if (ret) { 1371a6ec0b62STejun Heo scx_error(sch, "ops.cgroup_init() failed (%d)", ret); 1372a6ec0b62STejun Heo goto err; 1373a6ec0b62STejun Heo } 1374a6ec0b62STejun Heo } 1375a6ec0b62STejun Heo tg->scx.flags |= SCX_TG_SUB_INIT; 1376a6ec0b62STejun Heo } 1377a6ec0b62STejun Heo 1378a6ec0b62STejun Heo css_for_each_descendant_post(css, ecss) { 1379a6ec0b62STejun Heo struct task_group *tg = css_tg(css); 1380a6ec0b62STejun Heo 1381a6ec0b62STejun Heo /* 1382a6ec0b62STejun Heo * SUB_INIT is pass 1's progress mark: pass 2 and the err path 1383a6ec0b62STejun Heo * must visit exactly the tgs pass 1 inited. 1384a6ec0b62STejun Heo */ 1385a6ec0b62STejun Heo if (!(tg->scx.flags & SCX_TG_SUB_INIT)) 1386a6ec0b62STejun Heo continue; 1387a6ec0b62STejun Heo 1388a6ec0b62STejun Heo /* skip the exit if the parent's ops.cgroup_init() failed */ 1389a6ec0b62STejun Heo if ((tg->scx.flags & SCX_TG_INITED) && SCX_HAS_OP(parent, cgroup_exit)) 1390a6ec0b62STejun Heo SCX_CALL_OP(parent, cgroup_exit, NULL, css->cgroup); 1391a6ec0b62STejun Heo tg->scx.sched = sch; 1392a6ec0b62STejun Heo tg->scx.flags |= SCX_TG_INITED; 1393a6ec0b62STejun Heo tg->scx.flags &= ~SCX_TG_SUB_INIT; 1394a6ec0b62STejun Heo } 1395a6ec0b62STejun Heo 1396a6ec0b62STejun Heo return 0; 1397a6ec0b62STejun Heo 1398a6ec0b62STejun Heo err: 1399a6ec0b62STejun Heo css_for_each_descendant_post(css, ecss) { 1400a6ec0b62STejun Heo struct task_group *tg = css_tg(css); 1401a6ec0b62STejun Heo 1402a6ec0b62STejun Heo if (!(tg->scx.flags & SCX_TG_SUB_INIT)) 1403a6ec0b62STejun Heo continue; 1404a6ec0b62STejun Heo 1405a6ec0b62STejun Heo if (SCX_HAS_OP(sch, cgroup_exit)) 1406a6ec0b62STejun Heo SCX_CALL_OP(sch, cgroup_exit, NULL, css->cgroup); 1407a6ec0b62STejun Heo tg->scx.flags &= ~SCX_TG_SUB_INIT; 1408a6ec0b62STejun Heo } 1409a6ec0b62STejun Heo return ret; 1410a6ec0b62STejun Heo } 1411a6ec0b62STejun Heo 1412a6ec0b62STejun Heo /** 1413a6ec0b62STejun Heo * scx_cgroup_return_subtree - Return the subtree's cgroups to the parent sched 1414a6ec0b62STejun Heo * @sch: sub-scheduler being disabled 1415a6ec0b62STejun Heo * 1416a6ec0b62STejun Heo * Called while disabling @sch, after the subtree's cgrp->scx_sched's are reset 1417a6ec0b62STejun Heo * to the parent sched and before tasks are re-homed, mirroring root disable's 1418a6ec0b62STejun Heo * cgroups-before-tasks teardown order. The reverse of 1419a6ec0b62STejun Heo * scx_cgroup_claim_subtree(): exit @sch's cgroups from @sch, then init them on 1420a6ec0b62STejun Heo * the parent with the current tg->scx.* values, resyncing settings that changed 1421a6ec0b62STejun Heo * while @sch had them. 1422a6ec0b62STejun Heo * 1423a6ec0b62STejun Heo * When an init on the parent fails, the parent is failed - the same policy as 1424a6ec0b62STejun Heo * task re-homing. The remaining task_groups are punted: they move to the parent 1425a6ec0b62STejun Heo * anyway with %SCX_TG_INITED cleared, as ops.cgroup_init() failed or never ran 1426a6ec0b62STejun Heo * for them. A punted task_group gets no cgroup ops. The dying parent's own 1427a6ec0b62STejun Heo * disable moves it one sched up, initing it there. Root ends the chain: root 1428a6ec0b62STejun Heo * teardown drops cgroup ops entirely and the next enable's bulk init re-inits 1429a6ec0b62STejun Heo * every online task_group. 1430a6ec0b62STejun Heo * 1431a6ec0b62STejun Heo * The task re-home that follows still delivers ops.init_task() to the dying 1432a6ec0b62STejun Heo * parent, including for tasks in punted cgroups it never inited - tolerated 1433a6ec0b62STejun Heo * like the downstream failures of task punting (see scx_punt_task()). 1434a6ec0b62STejun Heo */ 1435a6ec0b62STejun Heo static void scx_cgroup_return_subtree(struct scx_sched *sch) 1436a6ec0b62STejun Heo { 1437a6ec0b62STejun Heo struct cgroup *sub_cgrp = sch_cgroup(sch); 1438a6ec0b62STejun Heo struct cgroup_subsys_state *ecss = cgroup_e_css(sub_cgrp, &cpu_cgrp_subsys); 1439a6ec0b62STejun Heo struct scx_sched *parent = scx_parent(sch); 1440a6ec0b62STejun Heo struct cgroup_subsys_state *css; 1441a6ec0b62STejun Heo bool parent_failed = false; 1442a6ec0b62STejun Heo int ret; 1443a6ec0b62STejun Heo 1444a6ec0b62STejun Heo css_for_each_descendant_post(css, ecss) { 1445a6ec0b62STejun Heo struct task_group *tg = css_tg(css); 1446a6ec0b62STejun Heo 1447a6ec0b62STejun Heo if (tg->scx.sched != sch || 1448a6ec0b62STejun Heo !cgroup_is_descendant(css->cgroup, sub_cgrp)) 1449a6ec0b62STejun Heo continue; 1450a6ec0b62STejun Heo 1451a6ec0b62STejun Heo /* skip the exit if @sch's ops.cgroup_init() failed for the tg */ 1452a6ec0b62STejun Heo if ((tg->scx.flags & SCX_TG_INITED) && SCX_HAS_OP(sch, cgroup_exit)) 1453a6ec0b62STejun Heo SCX_CALL_OP(sch, cgroup_exit, NULL, css->cgroup); 1454a6ec0b62STejun Heo tg->scx.sched = parent; 1455a6ec0b62STejun Heo tg->scx.flags |= SCX_TG_SUB_INIT; 1456a6ec0b62STejun Heo } 1457a6ec0b62STejun Heo 1458a6ec0b62STejun Heo css_for_each_descendant_pre(css, ecss) { 1459a6ec0b62STejun Heo struct task_group *tg = css_tg(css); 1460a6ec0b62STejun Heo struct scx_cgroup_init_args args = { 1461a6ec0b62STejun Heo .weight = tg->scx.weight, 1462a6ec0b62STejun Heo .bw_period_us = tg->scx.bw_period_us, 1463a6ec0b62STejun Heo .bw_quota_us = tg->scx.bw_quota_us, 1464a6ec0b62STejun Heo .bw_burst_us = tg->scx.bw_burst_us, 1465a6ec0b62STejun Heo }; 1466a6ec0b62STejun Heo 1467a6ec0b62STejun Heo /* the first pass must have transferred everything */ 1468a6ec0b62STejun Heo WARN_ON_ONCE(tg->scx.sched == sch); 1469a6ec0b62STejun Heo 1470a6ec0b62STejun Heo /* 1471a6ec0b62STejun Heo * SUB_INIT distinguishes the tgs pass 1 moved. The sched test 1472a6ec0b62STejun Heo * can't: a tg punted to the parent by an earlier failure would 1473a6ec0b62STejun Heo * also match. 1474a6ec0b62STejun Heo */ 1475a6ec0b62STejun Heo if (!(tg->scx.flags & SCX_TG_SUB_INIT)) 1476a6ec0b62STejun Heo continue; 1477a6ec0b62STejun Heo tg->scx.flags &= ~(SCX_TG_SUB_INIT | SCX_TG_INITED); 1478a6ec0b62STejun Heo 1479a6ec0b62STejun Heo /* 1480a6ec0b62STejun Heo * A re-init on $parent failed. The task_groups from here on are 1481a6ec0b62STejun Heo * punted: they stay on the dying $parent with INITED clear and 1482a6ec0b62STejun Heo * move onward when it disables. 1483a6ec0b62STejun Heo */ 1484a6ec0b62STejun Heo if (parent_failed) 1485a6ec0b62STejun Heo continue; 1486a6ec0b62STejun Heo 1487a6ec0b62STejun Heo if (SCX_HAS_OP(parent, cgroup_init)) { 1488a6ec0b62STejun Heo ret = SCX_CALL_OP_RET(parent, cgroup_init, NULL, css->cgroup, &args); 1489a6ec0b62STejun Heo if (ret) { 1490a6ec0b62STejun Heo scx_error(parent, "ops.cgroup_init() failed (%d) while disabling a sub-scheduler", 1491a6ec0b62STejun Heo ret); 1492a6ec0b62STejun Heo parent_failed = true; 1493a6ec0b62STejun Heo continue; 1494a6ec0b62STejun Heo } 1495a6ec0b62STejun Heo } 1496a6ec0b62STejun Heo tg->scx.flags |= SCX_TG_INITED; 1497a6ec0b62STejun Heo } 1498a6ec0b62STejun Heo } 1499a6ec0b62STejun Heo #else 1500a6ec0b62STejun Heo static inline s32 scx_cgroup_claim_subtree(struct scx_sched *sch) { return 0; } 1501a6ec0b62STejun Heo static inline void scx_cgroup_return_subtree(struct scx_sched *sch) {} 1502a6ec0b62STejun Heo #endif 1503a6ec0b62STejun Heo 1504daf8e166STejun Heo void scx_sub_disable(struct scx_sched *sch) 1505daf8e166STejun Heo { 1506daf8e166STejun Heo struct scx_sched *parent = scx_parent(sch); 1507daf8e166STejun Heo struct scx_task_iter sti; 1508daf8e166STejun Heo struct task_struct *p; 1509daf8e166STejun Heo int ret; 1510daf8e166STejun Heo 1511daf8e166STejun Heo /* 1512daf8e166STejun Heo * Guarantee forward progress and wait for descendants to be disabled. 1513daf8e166STejun Heo * To limit disruptions, $parent is not bypassed. Tasks are fully 1514daf8e166STejun Heo * prepped and then inserted back into $parent. 1515daf8e166STejun Heo */ 1516daf8e166STejun Heo scx_bypass(sch, true); 1517daf8e166STejun Heo drain_descendants(sch); 1518daf8e166STejun Heo 1519daf8e166STejun Heo /* 1520daf8e166STejun Heo * Here, every runnable task is guaranteed to make forward progress and 1521daf8e166STejun Heo * we can safely use blocking synchronization constructs. Actually 1522daf8e166STejun Heo * disable ops. 1523daf8e166STejun Heo */ 1524daf8e166STejun Heo mutex_lock(&scx_enable_mutex); 1525daf8e166STejun Heo percpu_down_write(&scx_fork_rwsem); 1526daf8e166STejun Heo scx_cgroup_lock(); 1527daf8e166STejun Heo 15287c2cd767STejun Heo /* 1529f883dbb6STejun Heo * An enable that failed before scx_link_sched() succeeded never owned a 1530f883dbb6STejun Heo * cgroup or task and won't be waited on by an ancestor's 1531f883dbb6STejun Heo * drain_descendants(). Nothing to reparent and walking the tasks can 1532f883dbb6STejun Heo * misbehave as the task ownership invariant (either owned by self or 1533f883dbb6STejun Heo * parent) does not hold. ->sibling can't identify this case - an undone 1534f883dbb6STejun Heo * link leaves it non-empty. 15357c2cd767STejun Heo */ 1536f883dbb6STejun Heo if (!sch->linked) 15377c2cd767STejun Heo goto dump; 15387c2cd767STejun Heo 1539daf8e166STejun Heo set_cgroup_sched(sch_cgroup(sch), parent); 1540daf8e166STejun Heo 1541a6ec0b62STejun Heo /* 1542a6ec0b62STejun Heo * Return the subtree's cgroups before re-homing tasks so that any 1543a6ec0b62STejun Heo * ops.init_task() on $parent only sees cgroups it has initialized. 1544a6ec0b62STejun Heo */ 1545a6ec0b62STejun Heo scx_cgroup_return_subtree(sch); 1546a6ec0b62STejun Heo 1547daf8e166STejun Heo scx_task_iter_start(&sti, sch->cgrp); 1548daf8e166STejun Heo while ((p = scx_task_iter_next_locked(&sti))) { 1549daf8e166STejun Heo struct rq *rq; 1550daf8e166STejun Heo struct rq_flags rf; 1551daf8e166STejun Heo 1552daf8e166STejun Heo /* filter out duplicate visits */ 1553daf8e166STejun Heo if (scx_task_on_sched(parent, p)) 1554daf8e166STejun Heo continue; 1555daf8e166STejun Heo 1556daf8e166STejun Heo /* 15577c2cd767STejun Heo * By the time control reaches here, all linked descendant 15587c2cd767STejun Heo * schedulers should have been disabled. 1559daf8e166STejun Heo */ 1560daf8e166STejun Heo WARN_ON_ONCE(!scx_task_on_sched(sch, p)); 1561daf8e166STejun Heo 1562daf8e166STejun Heo /* 1563daf8e166STejun Heo * @p is pinned by the iter: css_task_iter_next() takes a 1564daf8e166STejun Heo * reference and holds it until the next iter_next() call, so 1565daf8e166STejun Heo * @p->usage is guaranteed > 0. 1566daf8e166STejun Heo */ 1567daf8e166STejun Heo get_task_struct(p); 1568daf8e166STejun Heo 1569daf8e166STejun Heo scx_task_iter_unlock(&sti); 1570daf8e166STejun Heo 1571daf8e166STejun Heo /* 1572daf8e166STejun Heo * $p is READY or ENABLED on @sch. Initialize for $parent, 1573daf8e166STejun Heo * disable and exit from @sch, and then switch over to $parent. 1574daf8e166STejun Heo * 1575daf8e166STejun Heo * If a task fails to initialize for $parent, the only available 1576daf8e166STejun Heo * action is disabling $parent too. While this allows disabling 1577daf8e166STejun Heo * of a child sched to cause the parent scheduler to fail, the 1578daf8e166STejun Heo * failure can only originate from ops.init_task() of the 1579daf8e166STejun Heo * parent. A child can't directly affect the parent through its 1580daf8e166STejun Heo * own failures. 1581daf8e166STejun Heo */ 1582bf9dee58STejun Heo ret = __scx_init_task(parent, p, NULL, false); 1583daf8e166STejun Heo if (ret) { 1584daf8e166STejun Heo scx_fail_parent(sch, p, ret); 1585daf8e166STejun Heo put_task_struct(p); 1586daf8e166STejun Heo break; 1587daf8e166STejun Heo } 1588daf8e166STejun Heo 1589daf8e166STejun Heo rq = task_rq_lock(p, &rf); 1590daf8e166STejun Heo 1591daf8e166STejun Heo if (scx_get_task_state(p) == SCX_TASK_DEAD) { 1592daf8e166STejun Heo /* 1593daf8e166STejun Heo * sched_ext_dead() raced us between __scx_init_task() 1594daf8e166STejun Heo * and this rq lock and ran exit_task() on @sch (the 1595daf8e166STejun Heo * sched @p was on at that point), not on $parent. 1596daf8e166STejun Heo * $parent's just-completed init is owed an exit_task() 1597daf8e166STejun Heo * and we issue it here. 1598daf8e166STejun Heo */ 1599daf8e166STejun Heo scx_sub_init_cancel_task(parent, p); 1600daf8e166STejun Heo task_rq_unlock(rq, p, &rf); 1601daf8e166STejun Heo put_task_struct(p); 1602daf8e166STejun Heo continue; 1603daf8e166STejun Heo } 1604daf8e166STejun Heo 16050dc90ce1STejun Heo scx_rehome_task(parent, p); 1606daf8e166STejun Heo 1607daf8e166STejun Heo task_rq_unlock(rq, p, &rf); 1608daf8e166STejun Heo put_task_struct(p); 1609daf8e166STejun Heo } 1610daf8e166STejun Heo scx_task_iter_stop(&sti); 1611daf8e166STejun Heo 16127c2cd767STejun Heo dump: 1613daf8e166STejun Heo scx_disable_dump(sch); 1614daf8e166STejun Heo 1615daf8e166STejun Heo scx_cgroup_unlock(); 1616daf8e166STejun Heo percpu_up_write(&scx_fork_rwsem); 1617daf8e166STejun Heo 1618daf8e166STejun Heo /* 1619daf8e166STejun Heo * All tasks are moved off of @sch but there may still be on-going 1620daf8e166STejun Heo * operations (e.g. ops.select_cpu()). Drain them by flushing RCU. Use 1621daf8e166STejun Heo * the expedited version as ancestors may be waiting in bypass mode. 1622daf8e166STejun Heo * Also, tell the parent that there is no need to keep running bypass 1623daf8e166STejun Heo * DSQs for us. 1624daf8e166STejun Heo */ 1625daf8e166STejun Heo synchronize_rcu_expedited(); 1626daf8e166STejun Heo scx_disable_bypass_dsp(sch); 1627daf8e166STejun Heo 1628daf8e166STejun Heo scx_unlink_sched(sch); 1629daf8e166STejun Heo 1630daf8e166STejun Heo mutex_unlock(&scx_enable_mutex); 1631daf8e166STejun Heo 1632daf8e166STejun Heo /* 1633daf8e166STejun Heo * @sch is now unlinked from the parent's children list. Notify and call 1634daf8e166STejun Heo * ops.sub_detach/exit(). Note that ops.sub_detach/exit() must be called 1635daf8e166STejun Heo * after unlinking and releasing all locks. See scx_claim_exit(). 1636daf8e166STejun Heo */ 1637daf8e166STejun Heo wake_up_all(&scx_unlink_waitq); 1638daf8e166STejun Heo 1639daf8e166STejun Heo if (parent->ops.sub_detach && sch->sub_attached) { 1640daf8e166STejun Heo struct scx_sub_detach_args sub_detach_args = { 1641daf8e166STejun Heo .ops = &sch->ops, 1642daf8e166STejun Heo .cgroup_path = sch->cgrp_path, 1643daf8e166STejun Heo }; 1644daf8e166STejun Heo SCX_CALL_OP(parent, sub_detach, NULL, 1645daf8e166STejun Heo &sub_detach_args); 1646daf8e166STejun Heo } 1647daf8e166STejun Heo 1648daf8e166STejun Heo scx_log_sched_disable(sch); 1649daf8e166STejun Heo 1650daf8e166STejun Heo if (sch->ops.exit) 1651daf8e166STejun Heo SCX_CALL_OP(sch, exit, NULL, sch->exit_info); 165281507f14STejun Heo 165381507f14STejun Heo /* 165481507f14STejun Heo * @sch's non-ops programs such as timers and tracers can fire after 165581507f14STejun Heo * ops.exit(). Now that exit is complete, stop scx_prog_sched() from 165681507f14STejun Heo * resolving to @sch and drain in-flight resolvers. 165781507f14STejun Heo */ 165881507f14STejun Heo WRITE_ONCE(sch->dead, true); 165981507f14STejun Heo synchronize_rcu(); 166081507f14STejun Heo 1661daf8e166STejun Heo if (sch->sub_kset) 1662daf8e166STejun Heo kobject_del(&sch->sub_kset->kobj); 166380e6adaaSTejun Heo /* not added if enable failed before scx_sched_sysfs_add() */ 166480e6adaaSTejun Heo if (sch->kobj.state_in_sysfs) 1665daf8e166STejun Heo kobject_del(&sch->kobj); 1666daf8e166STejun Heo } 1667daf8e166STejun Heo 1668daf8e166STejun Heo /* verify that a scheduler can be attached to @cgrp and return the parent */ 1669daf8e166STejun Heo static struct scx_sched *find_parent_sched(struct cgroup *cgrp) 1670daf8e166STejun Heo { 167179474420STejun Heo struct scx_sched *parent = scx_cgroup_sched(cgrp); 1672daf8e166STejun Heo struct scx_sched *pos; 1673daf8e166STejun Heo 1674daf8e166STejun Heo lockdep_assert_held(&scx_sched_lock); 1675daf8e166STejun Heo 1676daf8e166STejun Heo /* can't attach twice to the same cgroup */ 1677daf8e166STejun Heo if (parent->cgrp == cgrp) 1678daf8e166STejun Heo return ERR_PTR(-EBUSY); 1679daf8e166STejun Heo 1680daf8e166STejun Heo /* does $parent allow sub-scheds? */ 1681daf8e166STejun Heo if (!parent->ops.sub_attach) 1682daf8e166STejun Heo return ERR_PTR(-EOPNOTSUPP); 1683daf8e166STejun Heo 1684daf8e166STejun Heo /* can't insert between $parent and its exiting children */ 1685daf8e166STejun Heo list_for_each_entry(pos, &parent->children, sibling) 1686daf8e166STejun Heo if (cgroup_is_descendant(pos->cgrp, cgrp)) 1687daf8e166STejun Heo return ERR_PTR(-EBUSY); 1688daf8e166STejun Heo 1689daf8e166STejun Heo return parent; 1690daf8e166STejun Heo } 1691daf8e166STejun Heo 1692daf8e166STejun Heo static bool assert_task_ready_or_enabled(struct task_struct *p) 1693daf8e166STejun Heo { 1694daf8e166STejun Heo u32 state = scx_get_task_state(p); 1695daf8e166STejun Heo 1696daf8e166STejun Heo switch (state) { 1697daf8e166STejun Heo case SCX_TASK_READY: 1698daf8e166STejun Heo case SCX_TASK_ENABLED: 1699daf8e166STejun Heo return true; 1700daf8e166STejun Heo default: 1701daf8e166STejun Heo WARN_ONCE(true, "sched_ext: Invalid task state %d for %s[%d] during enabling sub sched", 1702daf8e166STejun Heo state, p->comm, p->pid); 1703daf8e166STejun Heo return false; 1704daf8e166STejun Heo } 1705daf8e166STejun Heo } 1706daf8e166STejun Heo 1707daf8e166STejun Heo void scx_sub_enable_workfn(struct kthread_work *work) 1708daf8e166STejun Heo { 1709daf8e166STejun Heo struct scx_enable_cmd *cmd = container_of(work, struct scx_enable_cmd, work); 1710daf8e166STejun Heo struct sched_ext_ops *ops = cmd->ops; 1711daf8e166STejun Heo struct cgroup *cgrp; 1712daf8e166STejun Heo struct scx_sched *parent, *sch; 1713daf8e166STejun Heo struct scx_task_iter sti; 1714daf8e166STejun Heo struct task_struct *p; 1715daf8e166STejun Heo s32 i, ret; 1716daf8e166STejun Heo 1717daf8e166STejun Heo mutex_lock(&scx_enable_mutex); 1718daf8e166STejun Heo 1719daf8e166STejun Heo if (!scx_enabled()) { 1720daf8e166STejun Heo ret = -ENODEV; 1721daf8e166STejun Heo goto out_unlock; 1722daf8e166STejun Heo } 1723daf8e166STejun Heo 1724daf8e166STejun Heo /* See scx_root_enable_workfn() for the @ops->priv check. */ 1725daf8e166STejun Heo if (rcu_access_pointer(ops->priv)) { 1726daf8e166STejun Heo ret = -EBUSY; 1727daf8e166STejun Heo goto out_unlock; 1728daf8e166STejun Heo } 1729daf8e166STejun Heo 1730daf8e166STejun Heo cgrp = cgroup_get_from_id(ops->sub_cgroup_id); 1731daf8e166STejun Heo if (IS_ERR(cgrp)) { 1732daf8e166STejun Heo ret = PTR_ERR(cgrp); 1733daf8e166STejun Heo goto out_unlock; 1734daf8e166STejun Heo } 1735daf8e166STejun Heo 1736daf8e166STejun Heo raw_spin_lock_irq(&scx_sched_lock); 1737daf8e166STejun Heo parent = find_parent_sched(cgrp); 1738daf8e166STejun Heo if (IS_ERR(parent)) { 1739daf8e166STejun Heo raw_spin_unlock_irq(&scx_sched_lock); 1740daf8e166STejun Heo ret = PTR_ERR(parent); 1741daf8e166STejun Heo goto out_put_cgrp; 1742daf8e166STejun Heo } 1743daf8e166STejun Heo kobject_get(&parent->kobj); 1744daf8e166STejun Heo raw_spin_unlock_irq(&scx_sched_lock); 1745daf8e166STejun Heo 17468946dbd3STejun Heo /* 17478946dbd3STejun Heo * Flip the hot-path gates before ops->priv is published - the sub's 17488946dbd3STejun Heo * programs can e.g. kick cpus from that point on. The matching dec is 17498946dbd3STejun Heo * at the end of scx_sched_free_rcu_work(). 17508946dbd3STejun Heo */ 17518946dbd3STejun Heo static_branch_inc(&__scx_has_subs); 17528946dbd3STejun Heo 1753daf8e166STejun Heo /* scx_alloc_and_add_sched() consumes @cgrp whether it succeeds or not */ 1754daf8e166STejun Heo sch = scx_alloc_and_add_sched(cmd, cgrp, parent); 1755daf8e166STejun Heo kobject_put(&parent->kobj); 1756daf8e166STejun Heo if (IS_ERR(sch)) { 17578946dbd3STejun Heo static_branch_dec(&__scx_has_subs); 1758daf8e166STejun Heo ret = PTR_ERR(sch); 1759daf8e166STejun Heo goto out_unlock; 1760daf8e166STejun Heo } 1761daf8e166STejun Heo 176286094b95STejun Heo /* 176386094b95STejun Heo * Validate before scx_link_sched() publishes @sch, so an invalid sub 176486094b95STejun Heo * never becomes visible with an unallocated pshard. 176586094b95STejun Heo */ 176686094b95STejun Heo ret = scx_validate_ops(sch, ops); 176786094b95STejun Heo if (ret) 176886094b95STejun Heo goto err_disable; 176986094b95STejun Heo 1770*bb70e4fbSTejun Heo scx_rescue_check_timeout(sch); 1771*bb70e4fbSTejun Heo 177286094b95STejun Heo /* 177386094b95STejun Heo * Allocate pshard[] before scx_link_sched() publishes @sch into the 177486094b95STejun Heo * parent's RCU children list. A concurrent revoke walking the tree 177586094b95STejun Heo * would otherwise dereference sch->pshard[si] while it's still NULL. 177686094b95STejun Heo * Unlike the root path, the cid shard layout is stable at this point. 177786094b95STejun Heo * 177886094b95STejun Heo * scx_alloc_pshards() skips allocation when @sch's arena pool isn't 177986094b95STejun Heo * initialized, so scx_arena_pool_init() must run first. 178086094b95STejun Heo */ 178186094b95STejun Heo ret = scx_arena_pool_init(sch); 178286094b95STejun Heo if (ret) 178386094b95STejun Heo goto err_disable; 178486094b95STejun Heo 178586094b95STejun Heo ret = scx_alloc_pshards(sch); 178686094b95STejun Heo if (ret) 178786094b95STejun Heo goto err_disable; 178886094b95STejun Heo 1789daf8e166STejun Heo ret = scx_link_sched(sch); 1790daf8e166STejun Heo if (ret) 1791daf8e166STejun Heo goto err_disable; 1792daf8e166STejun Heo 179380e6adaaSTejun Heo ret = scx_sched_sysfs_add(sch); 179480e6adaaSTejun Heo if (ret) 179580e6adaaSTejun Heo goto err_disable; 179680e6adaaSTejun Heo 1797daf8e166STejun Heo if (sch->level >= SCX_SUB_MAX_DEPTH) { 1798daf8e166STejun Heo scx_error(sch, "max nesting depth %d violated", 1799daf8e166STejun Heo SCX_SUB_MAX_DEPTH); 180000a08ddfSCui Jian ret = -EINVAL; 1801daf8e166STejun Heo goto err_disable; 1802daf8e166STejun Heo } 1803daf8e166STejun Heo 1804daf8e166STejun Heo if (sch->ops.init) { 1805daf8e166STejun Heo ret = SCX_CALL_OP_RET(sch, init, NULL); 1806daf8e166STejun Heo if (ret) { 1807daf8e166STejun Heo ret = scx_ops_sanitize_err(sch, "init", ret); 1808daf8e166STejun Heo scx_error(sch, "ops.init() failed (%d)", ret); 1809daf8e166STejun Heo goto err_disable; 1810daf8e166STejun Heo } 1811daf8e166STejun Heo sch->exit_info->flags |= SCX_EFLAG_INITIALIZED; 1812daf8e166STejun Heo } 1813daf8e166STejun Heo 1814daf8e166STejun Heo ret = scx_set_cmask_scratch_alloc(sch); 1815daf8e166STejun Heo if (ret) 1816daf8e166STejun Heo goto err_disable; 1817daf8e166STejun Heo 1818daf8e166STejun Heo struct scx_sub_attach_args sub_attach_args = { 1819daf8e166STejun Heo .ops = &sch->ops, 1820daf8e166STejun Heo .cgroup_path = sch->cgrp_path, 1821daf8e166STejun Heo }; 1822daf8e166STejun Heo 1823daf8e166STejun Heo ret = SCX_CALL_OP_RET(parent, sub_attach, NULL, 1824daf8e166STejun Heo &sub_attach_args); 1825daf8e166STejun Heo if (ret) { 1826daf8e166STejun Heo ret = scx_ops_sanitize_err(sch, "sub_attach", ret); 1827daf8e166STejun Heo scx_error(sch, "parent rejected (%d)", ret); 1828daf8e166STejun Heo goto err_disable; 1829daf8e166STejun Heo } 1830daf8e166STejun Heo sch->sub_attached = true; 1831daf8e166STejun Heo 1832daf8e166STejun Heo scx_bypass(sch, true); 1833daf8e166STejun Heo 1834daf8e166STejun Heo for (i = SCX_OPI_BEGIN; i < SCX_OPI_END; i++) 1835daf8e166STejun Heo if (((void (**)(void))ops)[i]) 1836daf8e166STejun Heo set_bit(i, sch->has_op); 1837daf8e166STejun Heo 1838daf8e166STejun Heo percpu_down_write(&scx_fork_rwsem); 1839daf8e166STejun Heo scx_cgroup_lock(); 1840daf8e166STejun Heo 1841daf8e166STejun Heo /* 1842daf8e166STejun Heo * Set cgroup->scx_sched's and check CSS_ONLINE. Either we see 1843daf8e166STejun Heo * !CSS_ONLINE or scx_cgroup_lifetime_notify() sees and shoots us down. 1844daf8e166STejun Heo */ 1845daf8e166STejun Heo set_cgroup_sched(sch_cgroup(sch), sch); 1846daf8e166STejun Heo if (!(cgrp->self.flags & CSS_ONLINE)) { 1847daf8e166STejun Heo scx_error(sch, "cgroup is not online"); 184800a08ddfSCui Jian ret = -ENODEV; 1849daf8e166STejun Heo goto err_unlock_and_disable; 1850daf8e166STejun Heo } 1851daf8e166STejun Heo 1852daf8e166STejun Heo /* 1853a6ec0b62STejun Heo * Take over the subtree's cgroups before any task is claimed, 1854a6ec0b62STejun Heo * mirroring root enable's cgroups-before-tasks order. 1855a6ec0b62STejun Heo */ 1856a6ec0b62STejun Heo ret = scx_cgroup_claim_subtree(sch); 1857a6ec0b62STejun Heo if (ret) 1858a6ec0b62STejun Heo goto err_unlock_and_disable; 1859a6ec0b62STejun Heo 1860a6ec0b62STejun Heo /* 1861daf8e166STejun Heo * Initialize tasks for the new child $sch without exiting them for 1862daf8e166STejun Heo * $parent so that the tasks can always be reverted back to $parent 1863daf8e166STejun Heo * sched on child init failure. 1864daf8e166STejun Heo */ 1865daf8e166STejun Heo WARN_ON_ONCE(scx_enabling_sub_sched); 1866daf8e166STejun Heo scx_enabling_sub_sched = sch; 1867daf8e166STejun Heo 1868daf8e166STejun Heo scx_task_iter_start(&sti, sch->cgrp); 1869daf8e166STejun Heo while ((p = scx_task_iter_next_locked(&sti))) { 1870daf8e166STejun Heo struct rq *rq; 1871daf8e166STejun Heo struct rq_flags rf; 1872daf8e166STejun Heo 1873daf8e166STejun Heo /* 1874daf8e166STejun Heo * Task iteration may visit the same task twice when racing 1875daf8e166STejun Heo * against exiting. Use %SCX_TASK_SUB_INIT to mark tasks which 1876daf8e166STejun Heo * finished __scx_init_task() and skip if set. 1877daf8e166STejun Heo * 1878daf8e166STejun Heo * A task may exit and get freed between __scx_init_task() 1879daf8e166STejun Heo * completion and scx_enable_task(). In such cases, 1880daf8e166STejun Heo * scx_disable_and_exit_task() must exit the task for both the 1881daf8e166STejun Heo * parent and child scheds. 1882daf8e166STejun Heo */ 1883daf8e166STejun Heo if (p->scx.flags & SCX_TASK_SUB_INIT) 1884daf8e166STejun Heo continue; 1885daf8e166STejun Heo 1886daf8e166STejun Heo /* @p is pinned by the iter; see scx_sub_disable() */ 1887daf8e166STejun Heo get_task_struct(p); 1888daf8e166STejun Heo 1889daf8e166STejun Heo if (!assert_task_ready_or_enabled(p)) { 1890daf8e166STejun Heo ret = -EINVAL; 1891daf8e166STejun Heo goto abort; 1892daf8e166STejun Heo } 1893daf8e166STejun Heo 1894daf8e166STejun Heo scx_task_iter_unlock(&sti); 1895daf8e166STejun Heo 1896daf8e166STejun Heo /* 1897daf8e166STejun Heo * As $p is still on $parent, it can't be transitioned to INIT. 1898daf8e166STejun Heo * Let's worry about task state later. Use __scx_init_task(). 1899daf8e166STejun Heo */ 1900bf9dee58STejun Heo ret = __scx_init_task(sch, p, NULL, false); 1901daf8e166STejun Heo if (ret) 1902daf8e166STejun Heo goto abort; 1903daf8e166STejun Heo 1904daf8e166STejun Heo rq = task_rq_lock(p, &rf); 1905daf8e166STejun Heo 1906daf8e166STejun Heo if (scx_get_task_state(p) == SCX_TASK_DEAD) { 1907daf8e166STejun Heo /* 1908daf8e166STejun Heo * sched_ext_dead() raced us between __scx_init_task() 1909daf8e166STejun Heo * and this rq lock and ran exit_task() on $parent (the 1910daf8e166STejun Heo * sched @p was on at that point), not on @sch. @sch's 1911daf8e166STejun Heo * just-completed init is owed an exit_task() and we 1912daf8e166STejun Heo * issue it here. 1913daf8e166STejun Heo */ 1914daf8e166STejun Heo scx_sub_init_cancel_task(sch, p); 1915daf8e166STejun Heo task_rq_unlock(rq, p, &rf); 1916daf8e166STejun Heo put_task_struct(p); 1917daf8e166STejun Heo continue; 1918daf8e166STejun Heo } 1919daf8e166STejun Heo 1920daf8e166STejun Heo p->scx.flags |= SCX_TASK_SUB_INIT; 1921daf8e166STejun Heo task_rq_unlock(rq, p, &rf); 1922daf8e166STejun Heo 1923daf8e166STejun Heo put_task_struct(p); 1924daf8e166STejun Heo } 1925daf8e166STejun Heo scx_task_iter_stop(&sti); 1926daf8e166STejun Heo 1927daf8e166STejun Heo /* 1928daf8e166STejun Heo * All tasks are prepped. Disable/exit tasks for $parent and enable for 1929daf8e166STejun Heo * the new @sch. 1930daf8e166STejun Heo */ 1931daf8e166STejun Heo scx_task_iter_start(&sti, sch->cgrp); 1932daf8e166STejun Heo while ((p = scx_task_iter_next_locked(&sti))) { 1933daf8e166STejun Heo /* 1934daf8e166STejun Heo * Use clearing of %SCX_TASK_SUB_INIT to detect and skip 1935daf8e166STejun Heo * duplicate iterations. 1936daf8e166STejun Heo */ 1937daf8e166STejun Heo if (!(p->scx.flags & SCX_TASK_SUB_INIT)) 1938daf8e166STejun Heo continue; 1939daf8e166STejun Heo 1940daf8e166STejun Heo scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE) { 1941daf8e166STejun Heo /* 1942daf8e166STejun Heo * $p must be either READY or ENABLED. If ENABLED, 1943daf8e166STejun Heo * __scx_disabled_and_exit_task() first disables and 1944daf8e166STejun Heo * makes it READY. However, after exiting $p, it will 1945daf8e166STejun Heo * leave $p as READY. 1946daf8e166STejun Heo */ 1947daf8e166STejun Heo assert_task_ready_or_enabled(p); 1948daf8e166STejun Heo __scx_disable_and_exit_task(parent, p); 1949daf8e166STejun Heo 1950daf8e166STejun Heo /* 1951daf8e166STejun Heo * $p is now only initialized for @sch and READY, which 19527c2cd767STejun Heo * is what we want. Assign it to @sch and, if it's on 19537c2cd767STejun Heo * the ext class, enable. A non-ext task, possible under 19547c2cd767STejun Heo * an %SCX_OPS_SWITCH_PARTIAL root, stays READY and is 19557c2cd767STejun Heo * enabled by switching_to_scx() if it switches over. 1956daf8e166STejun Heo */ 1957daf8e166STejun Heo scx_set_task_sched(p, sch); 19587c2cd767STejun Heo if (p->sched_class == &ext_sched_class) 1959daf8e166STejun Heo scx_enable_task(sch, p); 1960daf8e166STejun Heo 1961daf8e166STejun Heo p->scx.flags &= ~SCX_TASK_SUB_INIT; 1962daf8e166STejun Heo } 1963daf8e166STejun Heo } 1964daf8e166STejun Heo scx_task_iter_stop(&sti); 1965daf8e166STejun Heo 1966daf8e166STejun Heo scx_enabling_sub_sched = NULL; 1967daf8e166STejun Heo 1968daf8e166STejun Heo scx_cgroup_unlock(); 1969daf8e166STejun Heo percpu_up_write(&scx_fork_rwsem); 1970daf8e166STejun Heo 1971daf8e166STejun Heo scx_bypass(sch, false); 1972daf8e166STejun Heo 19735f2a9a4cSTejun Heo /* @sch is enabled; deliver any caps owed since its sub_attach() */ 19745f2a9a4cSTejun Heo scx_sub_seed_caps(sch); 19755f2a9a4cSTejun Heo 1976daf8e166STejun Heo pr_info("sched_ext: BPF sub-scheduler \"%s\" enabled\n", sch->ops.name); 1977daf8e166STejun Heo kobject_uevent(&sch->kobj, KOBJ_ADD); 1978daf8e166STejun Heo ret = 0; 1979daf8e166STejun Heo goto out_unlock; 1980daf8e166STejun Heo 1981daf8e166STejun Heo out_put_cgrp: 1982daf8e166STejun Heo cgroup_put(cgrp); 1983daf8e166STejun Heo out_unlock: 1984daf8e166STejun Heo mutex_unlock(&scx_enable_mutex); 1985daf8e166STejun Heo cmd->ret = ret; 1986daf8e166STejun Heo return; 1987daf8e166STejun Heo 1988daf8e166STejun Heo abort: 1989daf8e166STejun Heo put_task_struct(p); 1990daf8e166STejun Heo scx_task_iter_stop(&sti); 1991daf8e166STejun Heo 1992daf8e166STejun Heo /* 1993daf8e166STejun Heo * Undo __scx_init_task() for tasks we marked. scx_enable_task() never 1994daf8e166STejun Heo * ran for @sch on them, so calling scx_disable_task() here would invoke 1995daf8e166STejun Heo * ops.disable() without a matching ops.enable(). scx_enabling_sub_sched 1996daf8e166STejun Heo * must stay set until SUB_INIT is cleared from every marked task - 1997daf8e166STejun Heo * scx_disable_and_exit_task() reads it when a task exits concurrently. 1998daf8e166STejun Heo */ 1999daf8e166STejun Heo scx_task_iter_start(&sti, sch->cgrp); 2000daf8e166STejun Heo while ((p = scx_task_iter_next_locked(&sti))) { 2001daf8e166STejun Heo if (p->scx.flags & SCX_TASK_SUB_INIT) { 2002daf8e166STejun Heo scx_sub_init_cancel_task(sch, p); 2003daf8e166STejun Heo p->scx.flags &= ~SCX_TASK_SUB_INIT; 2004daf8e166STejun Heo } 2005daf8e166STejun Heo } 2006daf8e166STejun Heo scx_task_iter_stop(&sti); 2007daf8e166STejun Heo scx_enabling_sub_sched = NULL; 2008daf8e166STejun Heo err_unlock_and_disable: 2009daf8e166STejun Heo /* we'll soon enter disable path, keep bypass on */ 2010daf8e166STejun Heo scx_cgroup_unlock(); 2011daf8e166STejun Heo percpu_up_write(&scx_fork_rwsem); 2012daf8e166STejun Heo err_disable: 2013daf8e166STejun Heo mutex_unlock(&scx_enable_mutex); 2014ad45691dSTejun Heo /* 2015ad45691dSTejun Heo * Some enable failures only return an errno (e.g. -ENOMEM from an 2016ad45691dSTejun Heo * allocation) without calling scx_error(). Record it so 2017ad45691dSTejun Heo * scx_flush_disable_work() runs the disable and ops.exit() fires. 2018ad45691dSTejun Heo */ 2019ad45691dSTejun Heo scx_error(sch, "scx_sub_enable() failed (%d)", ret); 2020daf8e166STejun Heo scx_flush_disable_work(sch); 2021daf8e166STejun Heo cmd->ret = 0; 2022daf8e166STejun Heo } 2023daf8e166STejun Heo 2024bf9dee58STejun Heo /** 2025bf9dee58STejun Heo * scx_cgroup_task_migrating - Prepare a task for a cgroup migration 2026bf9dee58STejun Heo * @ctx: migration being prepared 2027bf9dee58STejun Heo * 2028bf9dee58STejun Heo * A task's sched must match its cgroup's owner, so a migration that crosses a 2029bf9dee58STejun Heo * sched boundary re-homes the task once committed. Run the fallible part here, 2030bf9dee58STejun Heo * before the migration commits: initialize the task for the destination sched. 2031bf9dee58STejun Heo * A rejection fails the cgroup.procs write. 2032bf9dee58STejun Heo */ 2033bf9dee58STejun Heo static s32 scx_cgroup_task_migrating(struct cgroup_task_migrate_ctx *ctx) 2034bf9dee58STejun Heo { 2035bf9dee58STejun Heo struct task_struct *p = ctx->task; 2036bf9dee58STejun Heo struct scx_sched *to; 2037bf9dee58STejun Heo int ret; 2038bf9dee58STejun Heo 2039bf9dee58STejun Heo /* 2040bf9dee58STejun Heo * Cleared under scx_cgroup_lock() before root disable starts tearing 2041bf9dee58STejun Heo * down tasks. As cgroup_mutex is held, a set flag guarantees that the 2042bf9dee58STejun Heo * teardown loop is not running concurrently. 2043bf9dee58STejun Heo */ 2044bf9dee58STejun Heo if (!scx_cgroup_enabled) 2045bf9dee58STejun Heo return NOTIFY_OK; 2046bf9dee58STejun Heo 204779474420STejun Heo to = scx_cgroup_sched(ctx->dst_dcgrp); 2048bf9dee58STejun Heo if (scx_task_on_sched(to, p)) 2049bf9dee58STejun Heo return NOTIFY_OK; 2050bf9dee58STejun Heo 2051bf9dee58STejun Heo ret = __scx_init_task(to, p, ctx->dst_dcgrp, false); 2052bf9dee58STejun Heo if (ret) 2053bf9dee58STejun Heo return notifier_from_errno(ret); 2054bf9dee58STejun Heo 2055bf9dee58STejun Heo return NOTIFY_OK; 2056bf9dee58STejun Heo } 2057bf9dee58STejun Heo 2058bf9dee58STejun Heo /** 2059bf9dee58STejun Heo * scx_cgroup_task_migrated - Re-home a task that changed cgroups 2060bf9dee58STejun Heo * @ctx: committed migration 2061bf9dee58STejun Heo * 2062bf9dee58STejun Heo * Move the task to its new cgroup's sched, which scx_cgroup_task_migrating() 2063bf9dee58STejun Heo * already initialized it for. Can't fail. 2064bf9dee58STejun Heo * 2065bf9dee58STejun Heo * This is safe against all phases of the destination sched's destruction. A 2066bf9dee58STejun Heo * disable resets cgroup ownership to the parent and re-homes tasks in one 2067bf9dee58STejun Heo * scx_cgroup_lock() section. If that section already ran, the destination would 2068bf9dee58STejun Heo * be the parent. Otherwise, the re-home loop is still ahead and guaranteed to 2069bf9dee58STejun Heo * visit the task, now in the destination cgroup. 2070bf9dee58STejun Heo */ 2071bf9dee58STejun Heo static void scx_cgroup_task_migrated(struct cgroup_task_migrate_ctx *ctx) 2072bf9dee58STejun Heo { 2073bf9dee58STejun Heo struct task_struct *p = ctx->task; 2074bf9dee58STejun Heo struct scx_sched *to; 2075bf9dee58STejun Heo struct rq *rq; 2076bf9dee58STejun Heo struct rq_flags rf; 2077bf9dee58STejun Heo 2078bf9dee58STejun Heo if (!scx_cgroup_enabled) 2079bf9dee58STejun Heo return; 2080bf9dee58STejun Heo 208179474420STejun Heo to = scx_cgroup_sched(ctx->dst_dcgrp); 2082bf9dee58STejun Heo if (scx_task_on_sched(to, p)) 2083bf9dee58STejun Heo return; 2084bf9dee58STejun Heo 2085bf9dee58STejun Heo rq = task_rq_lock(p, &rf); 2086bf9dee58STejun Heo scx_rehome_task(to, p); 2087bf9dee58STejun Heo task_rq_unlock(rq, p, &rf); 2088bf9dee58STejun Heo } 2089bf9dee58STejun Heo 2090bf9dee58STejun Heo /** 2091bf9dee58STejun Heo * scx_cgroup_task_migrate_canceled - Undo migration preparation 2092bf9dee58STejun Heo * @ctx: canceled migration 2093bf9dee58STejun Heo * 2094bf9dee58STejun Heo * The migration failed after scx_cgroup_task_migrating() initialized the task 2095bf9dee58STejun Heo * for the destination sched. The task stays on its current sched in the source 2096bf9dee58STejun Heo * cgroup. Undo the destination's init. 2097bf9dee58STejun Heo */ 2098bf9dee58STejun Heo static void scx_cgroup_task_migrate_canceled(struct cgroup_task_migrate_ctx *ctx) 2099bf9dee58STejun Heo { 2100bf9dee58STejun Heo struct task_struct *p = ctx->task; 2101bf9dee58STejun Heo struct scx_sched *to; 2102bf9dee58STejun Heo struct rq *rq; 2103bf9dee58STejun Heo struct rq_flags rf; 2104bf9dee58STejun Heo 2105bf9dee58STejun Heo if (!scx_cgroup_enabled) 2106bf9dee58STejun Heo return; 2107bf9dee58STejun Heo 210879474420STejun Heo to = scx_cgroup_sched(ctx->dst_dcgrp); 2109bf9dee58STejun Heo if (scx_task_on_sched(to, p)) 2110bf9dee58STejun Heo return; 2111bf9dee58STejun Heo 2112bf9dee58STejun Heo rq = task_rq_lock(p, &rf); 2113bf9dee58STejun Heo scx_sub_init_cancel_task(to, p); 2114bf9dee58STejun Heo task_rq_unlock(rq, p, &rf); 2115bf9dee58STejun Heo } 2116bf9dee58STejun Heo 2117daf8e166STejun Heo static s32 scx_cgroup_lifetime_notify(struct notifier_block *nb, 2118daf8e166STejun Heo unsigned long action, void *data) 2119daf8e166STejun Heo { 2120daf8e166STejun Heo struct cgroup *cgrp = data; 2121daf8e166STejun Heo struct cgroup *parent = cgroup_parent(cgrp); 212279474420STejun Heo struct scx_sched *sch; 2123daf8e166STejun Heo 2124daf8e166STejun Heo if (!cgroup_on_dfl(cgrp)) 2125daf8e166STejun Heo return NOTIFY_OK; 2126daf8e166STejun Heo 2127daf8e166STejun Heo switch (action) { 2128daf8e166STejun Heo case CGROUP_LIFETIME_ONLINE: 2129daf8e166STejun Heo /* inherit ->scx_sched from $parent */ 2130daf8e166STejun Heo if (parent) 213179474420STejun Heo rcu_assign_pointer(cgrp->scx_sched, scx_cgroup_sched(parent)); 2132daf8e166STejun Heo break; 2133daf8e166STejun Heo case CGROUP_LIFETIME_OFFLINE: 2134daf8e166STejun Heo /* if there is a sched attached, shoot it down */ 213579474420STejun Heo sch = scx_cgroup_sched(cgrp); 213679474420STejun Heo if (sch && sch->cgrp == cgrp) 213779474420STejun Heo scx_exit(sch, SCX_EXIT_UNREG_KERN, 2138daf8e166STejun Heo SCX_ECODE_RSN_CGROUP_OFFLINE, 2139daf8e166STejun Heo "cgroup %llu going offline", cgroup_id(cgrp)); 2140daf8e166STejun Heo break; 2141daf8e166STejun Heo } 2142daf8e166STejun Heo 2143daf8e166STejun Heo return NOTIFY_OK; 2144daf8e166STejun Heo } 2145daf8e166STejun Heo 2146daf8e166STejun Heo static struct notifier_block scx_cgroup_lifetime_nb = { 2147daf8e166STejun Heo .notifier_call = scx_cgroup_lifetime_notify, 2148daf8e166STejun Heo }; 2149daf8e166STejun Heo 2150bf9dee58STejun Heo static s32 scx_cgroup_task_notify(struct notifier_block *nb, 2151bf9dee58STejun Heo unsigned long action, void *data) 2152daf8e166STejun Heo { 2153bf9dee58STejun Heo struct cgroup_task_migrate_ctx *ctx = data; 2154bf9dee58STejun Heo 2155bf9dee58STejun Heo switch (action) { 2156bf9dee58STejun Heo case CGROUP_TASK_MIGRATING: 2157bf9dee58STejun Heo return scx_cgroup_task_migrating(ctx); 2158bf9dee58STejun Heo case CGROUP_TASK_MIGRATED: 2159bf9dee58STejun Heo scx_cgroup_task_migrated(ctx); 2160bf9dee58STejun Heo break; 2161bf9dee58STejun Heo case CGROUP_TASK_MIGRATE_CANCELED: 2162bf9dee58STejun Heo scx_cgroup_task_migrate_canceled(ctx); 2163bf9dee58STejun Heo break; 2164daf8e166STejun Heo } 2165bf9dee58STejun Heo 2166bf9dee58STejun Heo return NOTIFY_OK; 2167bf9dee58STejun Heo } 2168bf9dee58STejun Heo 2169bf9dee58STejun Heo static struct notifier_block scx_cgroup_task_nb = { 2170bf9dee58STejun Heo .notifier_call = scx_cgroup_task_notify, 2171bf9dee58STejun Heo }; 2172bf9dee58STejun Heo 2173bf9dee58STejun Heo static s32 __init scx_cgroup_notifier_init(void) 2174bf9dee58STejun Heo { 2175bf9dee58STejun Heo s32 ret; 2176bf9dee58STejun Heo 2177bf9dee58STejun Heo ret = blocking_notifier_chain_register(&cgroup_lifetime_notifier, 2178bf9dee58STejun Heo &scx_cgroup_lifetime_nb); 2179bf9dee58STejun Heo if (ret) 2180bf9dee58STejun Heo return ret; 2181bf9dee58STejun Heo 2182bf9dee58STejun Heo return blocking_notifier_chain_register(&cgroup_task_notifier, 2183bf9dee58STejun Heo &scx_cgroup_task_nb); 2184bf9dee58STejun Heo } 2185bf9dee58STejun Heo core_initcall(scx_cgroup_notifier_init); 2186daf8e166STejun Heo 21875f2a9a4cSTejun Heo static void scx_pstack_recursion(struct bpf_prog *prog, const char *op) 2188daf8e166STejun Heo { 2189daf8e166STejun Heo struct scx_sched *sch; 2190daf8e166STejun Heo 2191daf8e166STejun Heo guard(rcu)(); 2192daf8e166STejun Heo sch = scx_prog_sched(prog->aux); 2193daf8e166STejun Heo if (unlikely(!sch)) 2194daf8e166STejun Heo return; 2195daf8e166STejun Heo 21965f2a9a4cSTejun Heo scx_error(sch, "%s recursion detected", op); 21975f2a9a4cSTejun Heo } 21985f2a9a4cSTejun Heo 21995f2a9a4cSTejun Heo void scx_pstack_recursion_on_dispatch(struct bpf_prog *prog) 22005f2a9a4cSTejun Heo { 22015f2a9a4cSTejun Heo scx_pstack_recursion(prog, "dispatch"); 22025f2a9a4cSTejun Heo } 22035f2a9a4cSTejun Heo 22045f2a9a4cSTejun Heo void scx_pstack_recursion_on_caps_updated(struct bpf_prog *prog) 22055f2a9a4cSTejun Heo { 22065f2a9a4cSTejun Heo scx_pstack_recursion(prog, "sub_caps_updated"); 2207daf8e166STejun Heo } 2208daf8e166STejun Heo 2209daf8e166STejun Heo __bpf_kfunc_start_defs(); 2210daf8e166STejun Heo 2211daf8e166STejun Heo /** 2212daf8e166STejun Heo * scx_bpf_sub_dispatch - Trigger dispatching on a child scheduler 2213daf8e166STejun Heo * @cgroup_id: cgroup ID of the child scheduler to dispatch 2214daf8e166STejun Heo * @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs 2215daf8e166STejun Heo * 2216daf8e166STejun Heo * Allows a parent scheduler to trigger dispatching on one of its direct 2217daf8e166STejun Heo * child schedulers. The child scheduler runs its dispatch operation to 2218daf8e166STejun Heo * move tasks from dispatch queues to the local runqueue. 2219daf8e166STejun Heo * 2220daf8e166STejun Heo * Returns: true on success, false if cgroup_id is invalid, not a direct 2221daf8e166STejun Heo * child, or caller lacks dispatch permission. 2222daf8e166STejun Heo */ 2223daf8e166STejun Heo __bpf_kfunc bool scx_bpf_sub_dispatch(u64 cgroup_id, const struct bpf_prog_aux *aux) 2224daf8e166STejun Heo { 2225daf8e166STejun Heo struct rq *this_rq = this_rq(); 2226daf8e166STejun Heo struct scx_sched *parent, *child; 2227daf8e166STejun Heo 2228daf8e166STejun Heo guard(rcu)(); 2229daf8e166STejun Heo parent = scx_prog_sched(aux); 2230daf8e166STejun Heo if (unlikely(!parent)) 2231daf8e166STejun Heo return false; 2232daf8e166STejun Heo 2233daf8e166STejun Heo child = scx_find_sub_sched(cgroup_id); 2234daf8e166STejun Heo 2235daf8e166STejun Heo if (unlikely(!child)) 2236daf8e166STejun Heo return false; 2237daf8e166STejun Heo 2238daf8e166STejun Heo if (unlikely(scx_parent(child) != parent)) { 2239daf8e166STejun Heo scx_error(parent, "trying to dispatch a distant sub-sched on cgroup %llu", 2240daf8e166STejun Heo cgroup_id); 2241daf8e166STejun Heo return false; 2242daf8e166STejun Heo } 2243daf8e166STejun Heo 2244147d1885STejun Heo /* 2245147d1885STejun Heo * Skip a child that does not effectively hold the base cap on this cpu: 2246147d1885STejun Heo * its inserts would only be rejected. ecaps are synced at the top of 2247147d1885STejun Heo * balance_one() before dispatch, so this reflects the in-effect state. 2248147d1885STejun Heo */ 2249147d1885STejun Heo if (scx_missing_caps(child, cpu_of(this_rq), SCX_CAP_BASE)) 2250147d1885STejun Heo return false; 2251147d1885STejun Heo 2252daf8e166STejun Heo return scx_dispatch_sched(child, this_rq, this_rq->scx.sub_dispatch_prev, 2253daf8e166STejun Heo true); 2254daf8e166STejun Heo } 2255daf8e166STejun Heo 225686094b95STejun Heo /* Validate common inputs. On success, *parent_out and *child_out are set. */ 225786094b95STejun Heo static s32 sub_cap_preamble(u64 cgroup_id, u64 caps, const struct bpf_prog_aux *aux, 225886094b95STejun Heo struct scx_sched **parent_out, struct scx_sched **child_out) 225986094b95STejun Heo { 226086094b95STejun Heo struct scx_sched *parent, *child; 226186094b95STejun Heo 226286094b95STejun Heo parent = scx_prog_sched(aux); 226386094b95STejun Heo if (unlikely(!parent)) 226486094b95STejun Heo return -ENODEV; 226586094b95STejun Heo 226686094b95STejun Heo if (!scx_is_cid_type()) { 226786094b95STejun Heo scx_error(parent, "sub-cap kfuncs require a cid-form scheduler"); 226886094b95STejun Heo return -EOPNOTSUPP; 226986094b95STejun Heo } 227086094b95STejun Heo 227186094b95STejun Heo child = scx_find_sub_sched(cgroup_id); 227286094b95STejun Heo if (unlikely(!child)) 227386094b95STejun Heo return -ENODEV; 227486094b95STejun Heo 227586094b95STejun Heo if (unlikely(scx_parent(child) != parent)) { 227686094b95STejun Heo scx_error(parent, "%s: sub-%llu is not a direct child", 227786094b95STejun Heo parent->cgrp_path, cgroup_id); 227886094b95STejun Heo return -EINVAL; 227986094b95STejun Heo } 228086094b95STejun Heo 228186094b95STejun Heo if (unlikely(caps & ~__SCX_CAP_ALL)) { 228286094b95STejun Heo scx_error(parent, "invalid caps 0x%llx", caps); 228386094b95STejun Heo return -EINVAL; 228486094b95STejun Heo } 228586094b95STejun Heo 228686094b95STejun Heo *parent_out = parent; 228786094b95STejun Heo *child_out = child; 228886094b95STejun Heo return 0; 228986094b95STejun Heo } 229086094b95STejun Heo 229186094b95STejun Heo /** 229286094b95STejun Heo * scx_bpf_sub_grant - Grant @caps on @cmask__ign's cids to a direct child 229386094b95STejun Heo * @cgroup_id: cgroup id of the direct child sub-sched 229486094b95STejun Heo * @caps: bitmask of SCX_CAP_* to grant 229586094b95STejun Heo * @cmask__ign: cid cmask to grant @caps on (arena pointer) 229686094b95STejun Heo * @denied_out__ign: optional arena cmask accumulating refused cids 229786094b95STejun Heo * @aux: implicit BPF argument 229886094b95STejun Heo * 229986094b95STejun Heo * A cid in @cmask__ign is granted to the child only if the parent holds every 230086094b95STejun Heo * requested cap on it. Refused cids are OR'd into @denied_out__ign when 230186094b95STejun Heo * provided. Refusals outside @denied_out__ign's range are not recorded. 230286094b95STejun Heo * 230386094b95STejun Heo * All-or-nothing keeps the caller-visible result binary per cid, so 230486094b95STejun Heo * @denied_out__ign is one mask to interpret rather than a per-cap matrix. 230586094b95STejun Heo * 230686094b95STejun Heo * Return 0 on full success, -EPERM if any cid was refused, or a negative 230786094b95STejun Heo * errno on other failures. 230886094b95STejun Heo */ 230986094b95STejun Heo __bpf_kfunc s32 scx_bpf_sub_grant(u64 cgroup_id, u64 caps, 231086094b95STejun Heo const struct scx_cmask *cmask__ign, 231186094b95STejun Heo struct scx_cmask *denied_out__ign, 231286094b95STejun Heo const struct bpf_prog_aux *aux) 231386094b95STejun Heo { 231486094b95STejun Heo struct scx_cmask_ref ref, denied_ref; 231586094b95STejun Heo struct scx_sched *parent, *child; 231686094b95STejun Heo bool any_denied = false; 23175f2a9a4cSTejun Heo LIST_HEAD(to_deliver); 231886094b95STejun Heo s32 si, ret; 231986094b95STejun Heo 232086094b95STejun Heo guard(irqsave)(); 232186094b95STejun Heo 232286094b95STejun Heo ret = sub_cap_preamble(cgroup_id, caps, aux, &parent, &child); 232386094b95STejun Heo if (ret) 232486094b95STejun Heo return ret; 232586094b95STejun Heo 232686094b95STejun Heo ret = scx_cmask_ref_init(parent, cmask__ign, &ref); 232786094b95STejun Heo if (ret) { 232886094b95STejun Heo scx_error(parent, "invalid cmask (%d)", ret); 232986094b95STejun Heo return ret; 233086094b95STejun Heo } 233186094b95STejun Heo 233286094b95STejun Heo if (denied_out__ign) { 233386094b95STejun Heo ret = scx_cmask_ref_init(parent, denied_out__ign, &denied_ref); 233486094b95STejun Heo if (ret) { 233586094b95STejun Heo scx_error(parent, "invalid denied_out (%d)", ret); 233686094b95STejun Heo return ret; 233786094b95STejun Heo } 233886094b95STejun Heo } 233986094b95STejun Heo 234086094b95STejun Heo /* apply the grant one shard at a time */ 234186094b95STejun Heo for (si = ref.shard_first; si < ref.shard_end; si++) { 234286094b95STejun Heo SCX_CMASK_DEFINE_SHARD(slice, 0, SCX_CID_SHARD_MAX_CPUS); 234386094b95STejun Heo struct scx_pshard *pps = parent->pshard[si]; 234486094b95STejun Heo struct scx_pshard *cps = child->pshard[si]; 23455f2a9a4cSTejun Heo u64 granted_caps = 0; 234686094b95STejun Heo u32 cap_bit; 234786094b95STejun Heo 234886094b95STejun Heo scx_cmask_ref_shard(&ref, si, slice); 234986094b95STejun Heo if (scx_cmask_empty(slice)) 235086094b95STejun Heo continue; 235186094b95STejun Heo 235286094b95STejun Heo SCX_CMASK_DEFINE_SHARD(granted_cids, slice->base, slice->nr_cids); 23535f2a9a4cSTejun Heo SCX_CMASK_DEFINE_SHARD(changed_cids, slice->base, slice->nr_cids); 23545f2a9a4cSTejun Heo SCX_CMASK_DEFINE_SHARD(delta, slice->base, slice->nr_cids); 23555f2a9a4cSTejun Heo 235686094b95STejun Heo scx_cmask_copy(granted_cids, slice); 235786094b95STejun Heo 235886094b95STejun Heo scoped_guard (raw_spinlock, &pps->lock) { 235986094b95STejun Heo guard(raw_spinlock_nested)(&cps->lock); 236086094b95STejun Heo 236186094b95STejun Heo /* 236286094b95STejun Heo * Narrow granted_cids to cids the parent holds every 236386094b95STejun Heo * requested cap on. All-or-nothing per cid. 236486094b95STejun Heo */ 236586094b95STejun Heo scx_for_each_cap_bit(cap_bit, caps) 236686094b95STejun Heo scx_cmask_and(granted_cids, &pps->caps[cap_bit].cmask); 236786094b95STejun Heo 23685f2a9a4cSTejun Heo /* 23695f2a9a4cSTejun Heo * For each requested cap, fold the newly-set cids into 23705f2a9a4cSTejun Heo * the child and accumulate the delta. 23715f2a9a4cSTejun Heo */ 23725f2a9a4cSTejun Heo scx_for_each_cap_bit(cap_bit, caps) { 23735f2a9a4cSTejun Heo struct scx_cmask *ccm = &cps->caps[cap_bit].cmask; 23745f2a9a4cSTejun Heo 23755f2a9a4cSTejun Heo scx_cmask_copy(delta, granted_cids); 23765f2a9a4cSTejun Heo scx_cmask_andnot(delta, ccm); 23775f2a9a4cSTejun Heo if (scx_cmask_empty(delta)) 23785f2a9a4cSTejun Heo continue; 23795f2a9a4cSTejun Heo 23805f2a9a4cSTejun Heo scx_cmask_or(ccm, delta); 23815f2a9a4cSTejun Heo scx_cmask_or(changed_cids, delta); 23825f2a9a4cSTejun Heo granted_caps |= BIT_U64(cap_bit); 23835f2a9a4cSTejun Heo } 23845f2a9a4cSTejun Heo 238556fdc35bSTejun Heo if (granted_caps) { 238656fdc35bSTejun Heo s32 cid; 238756fdc35bSTejun Heo 23885f2a9a4cSTejun Heo caps_updated_record(cps, changed_cids, granted_caps, 23895f2a9a4cSTejun Heo &to_deliver); 2390ca3aec45STejun Heo /* 2391ca3aec45STejun Heo * The sync arms an update_idle() re-notify if 2392ca3aec45STejun Heo * the cid gains baseline access, so the holder 2393ca3aec45STejun Heo * learns of an already-idle cid. 2394ca3aec45STejun Heo */ 239556fdc35bSTejun Heo scx_cmask_for_each_cid(cid, changed_cids) 239656fdc35bSTejun Heo queue_sync_ecaps(child, cid); 239756fdc35bSTejun Heo } 239886094b95STejun Heo } 239986094b95STejun Heo 240086094b95STejun Heo /* record cids that didn't make it through into @denied_out */ 240186094b95STejun Heo if (!scx_cmask_subset(slice, granted_cids)) { 240286094b95STejun Heo any_denied = true; 240386094b95STejun Heo if (denied_out__ign) { 240486094b95STejun Heo SCX_CMASK_DEFINE_SHARD(denied, slice->base, slice->nr_cids); 240586094b95STejun Heo 240686094b95STejun Heo scx_cmask_copy(denied, slice); 240786094b95STejun Heo scx_cmask_andnot(denied, granted_cids); 240886094b95STejun Heo scx_cmask_ref_or(&denied_ref, denied); 240986094b95STejun Heo } 241086094b95STejun Heo } 241186094b95STejun Heo } 24125f2a9a4cSTejun Heo 24135f2a9a4cSTejun Heo caps_updated_deliver(&to_deliver); 24145f2a9a4cSTejun Heo 241586094b95STejun Heo return any_denied ? -EPERM : 0; 241686094b95STejun Heo } 241786094b95STejun Heo 241886094b95STejun Heo /** 241986094b95STejun Heo * scx_bpf_sub_revoke - Revoke @caps on @cmask__ign's cids from @child 242086094b95STejun Heo * @cgroup_id: cgroup id of the direct child sub-sched 242186094b95STejun Heo * @caps: bitmask of SCX_CAP_* to revoke 242286094b95STejun Heo * @cmask__ign: cid cmask to revoke @caps on (arena pointer) 242386094b95STejun Heo * @aux: implicit BPF argument 242486094b95STejun Heo * 242586094b95STejun Heo * Clear @caps bits on @cmask__ign from the child named by @cgroup_id and all 242686094b95STejun Heo * its descendants. The origin parent's pshard lock is held across the subtree 242786094b95STejun Heo * walk so a concurrent grant from the origin parent observes the revoked 242886094b95STejun Heo * state. 242986094b95STejun Heo */ 243086094b95STejun Heo __bpf_kfunc void scx_bpf_sub_revoke(u64 cgroup_id, u64 caps, 243186094b95STejun Heo const struct scx_cmask *cmask__ign, 243286094b95STejun Heo const struct bpf_prog_aux *aux) 243386094b95STejun Heo { 243486094b95STejun Heo struct scx_cmask_ref ref; 243586094b95STejun Heo struct scx_sched *parent, *child, *pos; 24365f2a9a4cSTejun Heo LIST_HEAD(to_deliver); 243786094b95STejun Heo s32 si, ret; 243886094b95STejun Heo 243986094b95STejun Heo guard(irqsave)(); 244086094b95STejun Heo 244186094b95STejun Heo if (sub_cap_preamble(cgroup_id, caps, aux, &parent, &child)) 244286094b95STejun Heo return; 244386094b95STejun Heo 244486094b95STejun Heo ret = scx_cmask_ref_init(parent, cmask__ign, &ref); 244586094b95STejun Heo if (ret) { 244686094b95STejun Heo scx_error(parent, "invalid cmask (%d)", ret); 244786094b95STejun Heo return; 244886094b95STejun Heo } 244986094b95STejun Heo 245086094b95STejun Heo /* per-shard, walk child's subtree and clear @caps */ 245186094b95STejun Heo for (si = ref.shard_first; si < ref.shard_end; si++) { 245286094b95STejun Heo SCX_CMASK_DEFINE_SHARD(slice, 0, SCX_CID_SHARD_MAX_CPUS); 245386094b95STejun Heo 245486094b95STejun Heo scx_cmask_ref_shard(&ref, si, slice); 245586094b95STejun Heo if (scx_cmask_empty(slice)) 245686094b95STejun Heo continue; 245786094b95STejun Heo 245886094b95STejun Heo /* 245986094b95STejun Heo * Pre-order with subtree skip: a descendant that cleared 246086094b95STejun Heo * nothing means no descendant of it can hold @caps on these 246186094b95STejun Heo * cids either. 246286094b95STejun Heo */ 246386094b95STejun Heo guard(raw_spinlock)(&parent->pshard[si]->lock); 246486094b95STejun Heo pos = scx_next_descendant_pre(NULL, child); 246586094b95STejun Heo while (pos) { 246686094b95STejun Heo struct scx_pshard *ps = pos->pshard[si]; 24675f2a9a4cSTejun Heo SCX_CMASK_DEFINE_SHARD(changed_cids, slice->base, slice->nr_cids); 24685f2a9a4cSTejun Heo SCX_CMASK_DEFINE_SHARD(delta, slice->base, slice->nr_cids); 246986094b95STejun Heo u64 revoked_caps = 0; 247086094b95STejun Heo u32 cap_bit; 247186094b95STejun Heo 247286094b95STejun Heo scoped_guard (raw_spinlock_nested, &ps->lock) { 24735f2a9a4cSTejun Heo /* 24745f2a9a4cSTejun Heo * For each cap, clear lost cids and accumulate 24755f2a9a4cSTejun Heo * the per-cap diff for notification. 24765f2a9a4cSTejun Heo */ 247786094b95STejun Heo scx_for_each_cap_bit(cap_bit, caps) { 247886094b95STejun Heo struct scx_cmask *cm = &ps->caps[cap_bit].cmask; 247986094b95STejun Heo 24805f2a9a4cSTejun Heo scx_cmask_copy(delta, cm); 24815f2a9a4cSTejun Heo scx_cmask_and(delta, slice); 24825f2a9a4cSTejun Heo if (scx_cmask_empty(delta)) 248386094b95STejun Heo continue; 24845f2a9a4cSTejun Heo 24855f2a9a4cSTejun Heo scx_cmask_andnot(cm, delta); 24865f2a9a4cSTejun Heo scx_cmask_or(changed_cids, delta); 248786094b95STejun Heo revoked_caps |= BIT_U64(cap_bit); 248886094b95STejun Heo } 24895f2a9a4cSTejun Heo 249056fdc35bSTejun Heo if (revoked_caps) { 249156fdc35bSTejun Heo s32 cid; 249256fdc35bSTejun Heo 24935f2a9a4cSTejun Heo caps_updated_record(ps, changed_cids, revoked_caps, 24945f2a9a4cSTejun Heo &to_deliver); 249556fdc35bSTejun Heo scx_cmask_for_each_cid(cid, changed_cids) 249656fdc35bSTejun Heo queue_sync_ecaps(pos, cid); 249756fdc35bSTejun Heo } 249886094b95STejun Heo } 249986094b95STejun Heo 250086094b95STejun Heo if (revoked_caps) 250186094b95STejun Heo pos = scx_next_descendant_pre(pos, child); 250286094b95STejun Heo else 250386094b95STejun Heo pos = scx_skip_subtree_pre(pos, child); 250486094b95STejun Heo } 250586094b95STejun Heo } 25065f2a9a4cSTejun Heo 25075f2a9a4cSTejun Heo caps_updated_deliver(&to_deliver); 250886094b95STejun Heo } 250986094b95STejun Heo 251086094b95STejun Heo /** 251186094b95STejun Heo * scx_bpf_sub_caps - Read self's or a direct child's cap cmasks 251286094b95STejun Heo * @cgroup_id: 0 for self, or a direct child's cgroup id 251386094b95STejun Heo * @caps: one or more SCX_CAP_* bits 251486094b95STejun Heo * @out__ign: arena cmask to receive the union of @caps within its range 251586094b95STejun Heo * @aux: implicit BPF argument 251686094b95STejun Heo * 251786094b95STejun Heo * Read the cap cmasks granted on each cid for self (@cgroup_id 0) or a direct 251886094b95STejun Heo * child - the literal granted set. A sched can read only itself or a direct 251986094b95STejun Heo * child. 252086094b95STejun Heo * 252186094b95STejun Heo * Return 0, -ENODEV if @cgroup_id names no direct child, or -EINVAL on bad 252286094b95STejun Heo * inputs. 252386094b95STejun Heo */ 252486094b95STejun Heo __bpf_kfunc s32 scx_bpf_sub_caps(u64 cgroup_id, u64 caps, struct scx_cmask *out__ign, 252586094b95STejun Heo const struct bpf_prog_aux *aux) 252686094b95STejun Heo { 252786094b95STejun Heo struct scx_cmask_ref ref; 252886094b95STejun Heo struct scx_sched *sch, *target; 252986094b95STejun Heo struct scx_pshard **pshard; 253086094b95STejun Heo s32 si, ret; 253186094b95STejun Heo 253286094b95STejun Heo guard(irqsave)(); 253386094b95STejun Heo 253486094b95STejun Heo sch = scx_prog_sched(aux); 253586094b95STejun Heo if (unlikely(!sch)) 253686094b95STejun Heo return -ENODEV; 253786094b95STejun Heo 253886094b95STejun Heo if (!scx_is_cid_type()) { 253986094b95STejun Heo scx_error(sch, "sub-cap kfuncs require a cid-form scheduler"); 254086094b95STejun Heo return -EOPNOTSUPP; 254186094b95STejun Heo } 254286094b95STejun Heo 254386094b95STejun Heo if (unlikely(caps & ~__SCX_CAP_ALL)) { 254486094b95STejun Heo scx_error(sch, "invalid caps 0x%llx", caps); 254586094b95STejun Heo return -EINVAL; 254686094b95STejun Heo } 254786094b95STejun Heo 254886094b95STejun Heo /* @cgroup_id 0 reads self, otherwise a direct child */ 254986094b95STejun Heo if (cgroup_id) { 255086094b95STejun Heo target = scx_find_sub_sched(cgroup_id); 255186094b95STejun Heo if (unlikely(!target)) 255286094b95STejun Heo return -ENODEV; 255386094b95STejun Heo if (unlikely(scx_parent(target) != sch)) { 255486094b95STejun Heo scx_error(sch, "%s: sub-%llu is not a direct child", 255586094b95STejun Heo sch->cgrp_path, cgroup_id); 255686094b95STejun Heo return -EINVAL; 255786094b95STejun Heo } 255886094b95STejun Heo } else { 255986094b95STejun Heo target = sch; 256086094b95STejun Heo } 256186094b95STejun Heo 256286094b95STejun Heo /* 256386094b95STejun Heo * The target's caps storage may not be set up yet (e.g. a self-read 256486094b95STejun Heo * during ops.init_cids()). Pairs with the publish in 25653a773220STejun Heo * scx_alloc_pshards(): a non-NULL pshard has every element set and the 25663a773220STejun Heo * acquire also orders the cid table reads below against it. 256786094b95STejun Heo */ 25683a773220STejun Heo pshard = smp_load_acquire(&target->pshard); 256986094b95STejun Heo if (unlikely(!pshard)) { 257086094b95STejun Heo scx_error(sch, "scx_bpf_sub_caps() called before caps storage is initialized"); 257186094b95STejun Heo return -ENODEV; 257286094b95STejun Heo } 257386094b95STejun Heo 257486094b95STejun Heo ret = scx_cmask_ref_init(sch, out__ign, &ref); 257586094b95STejun Heo if (ret) { 257686094b95STejun Heo scx_error(sch, "invalid out (%d)", ret); 257786094b95STejun Heo return ret; 257886094b95STejun Heo } 257986094b95STejun Heo 258086094b95STejun Heo for (si = ref.shard_first; si < ref.shard_end; si++) { 25813a773220STejun Heo const struct scx_cid_shard *shard = 25823a773220STejun Heo &rcu_dereference_all(scx_cid_shard_ranges)[si]; 258386094b95STejun Heo SCX_CMASK_DEFINE_SHARD(local_out, shard->base_cid, shard->nr_cids); 258486094b95STejun Heo u32 cap_bit; 258586094b95STejun Heo 258686094b95STejun Heo scx_for_each_cap_bit(cap_bit, caps) 258786094b95STejun Heo scx_cmask_or(local_out, &pshard[si]->caps[cap_bit].cmask); 258886094b95STejun Heo scx_cmask_ref_copy(&ref, local_out); 258986094b95STejun Heo } 259086094b95STejun Heo return 0; 259186094b95STejun Heo } 259286094b95STejun Heo 2593b0a2ca6aSTejun Heo /** 2594b0a2ca6aSTejun Heo * scx_bpf_sub_kill_bstr - Kill a direct child sub-scheduler 2595b0a2ca6aSTejun Heo * @cgroup_id: cgroup id of the direct child to kill 2596b0a2ca6aSTejun Heo * @fmt: reason message format string 2597b0a2ca6aSTejun Heo * @data: format string parameters packaged using ___bpf_fill() macro 2598b0a2ca6aSTejun Heo * @data__sz: @data len, must end in '__sz' for the verifier 2599b0a2ca6aSTejun Heo * @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs 2600b0a2ca6aSTejun Heo * 2601b0a2ca6aSTejun Heo * Evict a direct child sub-scheduler, disabling it with the supplied reason. 2602b0a2ca6aSTejun Heo * The child and its subtree are torn down asynchronously through the usual 2603b0a2ca6aSTejun Heo * disable path. 2604b0a2ca6aSTejun Heo * 2605b0a2ca6aSTejun Heo * Unlike scx_bpf_exit(), no exit code is taken: the child is a separate 2606b0a2ca6aSTejun Heo * scheduler with its own exit-code semantics, so a code chosen by the parent 2607b0a2ca6aSTejun Heo * would have no defined meaning. The reason string carries the intent. 2608b0a2ca6aSTejun Heo * 2609b0a2ca6aSTejun Heo * Return 0 on success or -ENODEV if @cgroup_id names no sub-scheduler, which 2610b0a2ca6aSTejun Heo * can race with the child detaching on its own and so is not a scheduler error. 2611b0a2ca6aSTejun Heo * Naming a sched that exists but is not a direct child aborts the parent. 2612b0a2ca6aSTejun Heo */ 2613b0a2ca6aSTejun Heo __printf(2, 0) 2614b0a2ca6aSTejun Heo __bpf_kfunc s32 scx_bpf_sub_kill_bstr(u64 cgroup_id, char *fmt, 2615b0a2ca6aSTejun Heo unsigned long long *data, u32 data__sz, 2616b0a2ca6aSTejun Heo const struct bpf_prog_aux *aux) 2617b0a2ca6aSTejun Heo { 2618b0a2ca6aSTejun Heo struct scx_sched *parent, *child; 2619b0a2ca6aSTejun Heo 2620b0a2ca6aSTejun Heo guard(rcu)(); 2621b0a2ca6aSTejun Heo 2622b0a2ca6aSTejun Heo parent = scx_prog_sched(aux); 2623b0a2ca6aSTejun Heo if (unlikely(!parent)) 2624b0a2ca6aSTejun Heo return -ENODEV; 2625b0a2ca6aSTejun Heo 2626b0a2ca6aSTejun Heo if (!scx_is_cid_type()) { 2627b0a2ca6aSTejun Heo scx_error(parent, "sub-cap kfuncs require a cid-form scheduler"); 2628b0a2ca6aSTejun Heo return -EOPNOTSUPP; 2629b0a2ca6aSTejun Heo } 2630b0a2ca6aSTejun Heo 2631b0a2ca6aSTejun Heo child = scx_find_sub_sched(cgroup_id); 2632b0a2ca6aSTejun Heo if (unlikely(!child)) 2633b0a2ca6aSTejun Heo return -ENODEV; 2634b0a2ca6aSTejun Heo 2635b0a2ca6aSTejun Heo if (unlikely(scx_parent(child) != parent)) { 2636b0a2ca6aSTejun Heo scx_error(parent, "%s: sub-%llu is not a direct child", 2637b0a2ca6aSTejun Heo parent->cgrp_path, cgroup_id); 2638b0a2ca6aSTejun Heo return -EINVAL; 2639b0a2ca6aSTejun Heo } 2640b0a2ca6aSTejun Heo 26411bf623ebSTejun Heo scx_exit_bstr(child, SCX_EXIT_PARENT_KILL, 0, parent, fmt, data, data__sz); 2642b0a2ca6aSTejun Heo return 0; 2643b0a2ca6aSTejun Heo } 2644b0a2ca6aSTejun Heo 2645daf8e166STejun Heo __bpf_kfunc_end_defs(); 2646daf8e166STejun Heo 2647daf8e166STejun Heo #endif /* CONFIG_EXT_SUB_SCHED */ 2648