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