summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTejun Heo <tj@kernel.org>2026-10-13 11:26:22 -1000
committerTejun Heo <tj@kernel.org>2026-10-13 11:26:22 -1000
commita5ba5c61951dc00d296c79e96f0259b87861ce8c (patch)
tree0c9f088e82abfb32ee90987b0dd3b533b4fd14e6
parentd1350c523f481769182604d758ecfcdf3597b402 (diff)
parent2935062be77113c1c4565ca9d0c72180449aafee (diff)
sched_ext: Merge branch 'for-7.3-fixes' into for-7.4
Pull to receive 2935062be771 ("sched_ext: Wait for SCX_OPSS_DISPATCHING before moving tasks off DSQs") and resolve its conflicts with the proxy execution and reject DSQ work here so that for-next merges clean. Conflicts: - unlink_dsq_and_switch_rq_lock(): efa7bc7d7195 ("sched_ext: Avoid relocking DSQ during remote consumption") clears p->scx.dsq in the success branch and 2935062be771 adds the wait there. Kept both, the wait after the clear, so that the proxy reject in consume_remote_task() is covered too. - scx_reenq_reject(): 18f5c699d1d0 ("sched_ext: Rename scx_reenq_wait_dispatching() to scx_wait_dispatching()") renames the call in sub.c while cea8b60e4aee ("sched_ext: Move reject DSQ draining into core") moved the function into ext.c. Dropped the sub.c side and applied the rename at the new home. Signed-off-by: Tejun Heo <tj@kernel.org>
-rw-r--r--kernel/sched/ext/ext.c77
-rw-r--r--kernel/sched/ext/idle.c50
-rw-r--r--kernel/sched/ext/internal.h2
3 files changed, 88 insertions, 41 deletions
diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c
index 0e4174fa75b6..a2e0382ce3dd 100644
--- a/kernel/sched/ext/ext.c
+++ b/kernel/sched/ext/ext.c
@@ -2596,11 +2596,14 @@ static void wakeup_preempt_scx(struct rq *rq, struct task_struct *p, int wake_fl
void scx_move_local_task_to_local_dsq(struct scx_sched *sch, struct task_struct *p,
u64 enq_flags, struct rq *dst_rq)
{
- struct scx_dispatch_q *dst_dsq = scx_resolve_local_dsq(sch, dst_rq, p, &enq_flags);
+ struct scx_dispatch_q *dst_dsq;
lockdep_assert_rq_held(dst_rq);
WARN_ON_ONCE(p->scx.holding_cpu >= 0);
+ scx_wait_dispatching(p);
+
+ dst_dsq = scx_resolve_local_dsq(sch, dst_rq, p, &enq_flags);
if (enq_flags & (SCX_ENQ_HEAD | SCX_ENQ_PREEMPT | SCX_ENQ_PREEMPT_LAZY))
dsq_insert_head(dst_dsq, p);
@@ -2858,6 +2861,7 @@ static bool unlink_dsq_and_switch_rq_lock(struct task_struct *p,
* takes the !dsq path instead of retaking @dsq->lock.
*/
p->scx.dsq = NULL;
+ scx_wait_dispatching(p);
return true;
}
@@ -2917,6 +2921,8 @@ static struct rq *move_task_between_dsqs(struct scx_sched *sch,
lockdep_assert_held(&src_dsq->lock);
lockdep_assert_rq_held(src_rq);
+ scx_wait_dispatching(p);
+
if (dst_dsq->id == SCX_DSQ_LOCAL) {
dst_rq = container_of(dst_dsq, struct rq, scx.local_dsq);
/*
@@ -4882,11 +4888,12 @@ static bool local_task_should_reenq(struct rq *rq, struct task_struct *p,
}
/*
- * The dispatcher stores the final ops_state after dropping the DSQ lock, so @p
- * can be found on a DSQ while still %SCX_OPSS_DISPATCHING. Reenqueueing @p
- * before that store lands would have it clobber the new %SCX_OPSS_QUEUED.
+ * The dispatcher runs ops.dequeue() and stores the final ops_state after
+ * dropping the DSQ lock, so @p can be found on a DSQ while still
+ * %SCX_OPSS_DISPATCHING. Wait for the dispatch to finish before operating on
+ * @p.
*/
-void scx_reenq_wait_dispatching(struct task_struct *p)
+void scx_wait_dispatching(struct task_struct *p)
{
if (unlikely(atomic_long_read_acquire(&p->scx.ops_state) == SCX_OPSS_DISPATCHING))
wait_ops_state(p, SCX_OPSS_DISPATCHING);
@@ -4935,7 +4942,7 @@ static u32 reenq_local(struct scx_sched *sch, struct rq *rq, u64 reenq_flags)
if (!local_task_should_reenq(rq, p, &reenq_flags, &reason))
continue;
- scx_reenq_wait_dispatching(p);
+ scx_wait_dispatching(p);
scx_dispatch_dequeue(rq, p);
WARN_ON_ONCE(p->scx.flags & SCX_TASK_REENQ_REASON_MASK);
@@ -5057,7 +5064,7 @@ static void reenq_user(struct rq *rq, struct scx_dispatch_q *dsq, u64 reenq_flag
}
/* @p is on @dsq, its rq and @dsq are locked */
- scx_reenq_wait_dispatching(p);
+ scx_wait_dispatching(p);
dispatch_dequeue_locked(p, dsq);
raw_spin_unlock(&dsq->lock);
@@ -5168,7 +5175,7 @@ static void scx_reenq_reject(struct rq *rq)
WARN_ON_ONCE(p->migration_pending);
}
- scx_reenq_wait_dispatching(p);
+ scx_wait_dispatching(p);
scx_dispatch_dequeue(rq, p);
list_add_tail(&p->scx.dsq_list.node, &tasks);
@@ -6560,15 +6567,12 @@ static void enable_bypass_dsp(struct scx_sched *sch)
/*
* When a sub-sched bypasses, its tasks are queued on the bypass DSQs of
- * the nearest non-bypassing ancestor or root. As enable_bypass_dsp() is
- * called iff @sch is not already bypassed due to an ancestor bypassing,
- * we can assume that the parent is not bypassing and thus will be the
- * host of the bypass DSQs.
- *
- * While the situation may change in the future, the following
- * guarantees that the nearest non-bypassing ancestor or root has bypass
- * dispatch enabled while a descendant is bypassing, which is all that's
- * required.
+ * the nearest non-bypassing ancestor or root. The parent hosts them
+ * unless it is bypassing too. Then they sit further up and the parent
+ * consumes only its own bypass DSQs, empty until it leaves bypass and
+ * becomes the host. Either way the nearest non-bypassing ancestor or
+ * root has bypass dispatch enabled while a descendant is bypassing,
+ * which is all that's required.
*
* scx_bypass_dsp_enabled() test is used to determine whether to enter
* the bypass dispatch handling path from both bypassing and hosting
@@ -6677,35 +6681,46 @@ void scx_bypass(struct scx_sched *sch, bool bypass)
struct scx_sched *pos;
unsigned long flags;
int cpu;
+ bool changed;
raw_spin_lock_irqsave(&scx_bypass_lock, flags);
if (bypass) {
- if (!inc_bypass_depth(sch))
- goto unlock;
-
- enable_bypass_dsp(sch);
+ changed = inc_bypass_depth(sch);
+ if (changed)
+ enable_bypass_dsp(sch);
} else {
- if (!dec_bypass_depth(sch))
- goto unlock;
+ changed = dec_bypass_depth(sch);
}
/*
* Bypass state is propagated to all descendants - an scx_sched bypasses
- * if itself or any of its ancestors are in bypass mode.
+ * if itself or any of its ancestors are in bypass mode. A descendant's
+ * depth is the sum of its own bypasses and its ancestors', so every
+ * call propagates whether or not @sch changed state. The descendants'
+ * claims are released below with @sch's, after their tasks have moved.
*/
raw_spin_lock(&scx_sched_lock);
scx_for_each_descendant_pre(pos, sch) {
if (pos == sch)
continue;
- if (bypass)
- inc_bypass_depth(pos);
- else
+ if (bypass) {
+ if (inc_bypass_depth(pos))
+ enable_bypass_dsp(pos);
+ } else {
dec_bypass_depth(pos);
+ }
}
raw_spin_unlock(&scx_sched_lock);
/*
+ * A descendant's depth is at least @sch's, so no bypass state changed
+ * unless @sch's did.
+ */
+ if (!changed)
+ goto unlock;
+
+ /*
* No task property is changing. We just need to make sure all currently
* queued tasks are re-queued according to the new scx_bypassing()
* state. As an optimization, walk each rq's runnable_list instead of
@@ -6783,8 +6798,12 @@ void scx_bypass(struct scx_sched *sch, bool bypass)
}
/* disarming must come after moving all tasks out of the bypass DSQs */
- if (!bypass)
- scx_disable_bypass_dsp(sch);
+ if (!bypass) {
+ guard(raw_spinlock)(&scx_sched_lock);
+ scx_for_each_descendant_pre(pos, sch)
+ if (!pos->bypass_depth)
+ scx_disable_bypass_dsp(pos);
+ }
unlock:
raw_spin_unlock_irqrestore(&scx_bypass_lock, flags);
}
diff --git a/kernel/sched/ext/idle.c b/kernel/sched/ext/idle.c
index de5ec21384b0..922fbc3065cc 100644
--- a/kernel/sched/ext/idle.c
+++ b/kernel/sched/ext/idle.c
@@ -962,12 +962,43 @@ static bool is_bpf_migration_disabled(const struct task_struct *p)
return p->migration_disabled;
}
+static s32 select_cpu_restricted(struct task_struct *p, s32 prev_cpu,
+ const struct cpumask *allowed, u64 flags,
+ bool migration_disabled)
+{
+ s32 cpu;
+
+ if (migration_disabled)
+ cpu = task_cpu(p);
+ else
+ cpu = cpumask_first(p->cpus_ptr);
+
+ if (!cpumask_test_cpu(cpu, p->cpus_ptr))
+ return -EBUSY;
+
+ if (allowed && !cpumask_test_cpu(cpu, allowed))
+ return -EBUSY;
+
+ if (flags & SCX_PICK_IDLE_IN_NODE) {
+ int node = scx_cpu_node_if_enabled(prev_cpu);
+
+ if (scx_cpu_node_if_enabled(cpu) != node)
+ return -EBUSY;
+ }
+
+ if (!scx_idle_test_and_clear_cpu(cpu))
+ return -EBUSY;
+
+ return cpu;
+}
+
static s32 select_cpu_from_kfunc(struct scx_sched *sch, struct task_struct *p,
s32 prev_cpu, u64 wake_flags,
const struct cpumask *allowed, u64 flags)
{
unsigned long irq_flags;
bool we_locked = false;
+ bool migration_disabled;
s32 cpu;
if (!scx_cpu_valid(sch, prev_cpu, NULL))
@@ -1004,20 +1035,17 @@ static s32 select_cpu_from_kfunc(struct scx_sched *sch, struct task_struct *p,
/*
* This may also be called from ops.enqueue(), so we need to handle
- * per-CPU tasks as well. For these tasks, we can skip all idle CPU
- * selection optimizations and simply check whether the previously
- * used CPU is idle and within the allowed cpumask.
+ * per-CPU tasks as well. For these tasks, skip general idle CPU
+ * selection and validate the task's only usable CPU directly.
*/
- if (p->nr_cpus_allowed == 1 || is_bpf_migration_disabled(p)) {
- if (cpumask_test_cpu(prev_cpu, allowed ?: p->cpus_ptr) &&
- scx_idle_test_and_clear_cpu(prev_cpu))
- cpu = prev_cpu;
- else
- cpu = -EBUSY;
- } else {
+ migration_disabled = is_bpf_migration_disabled(p);
+
+ if (p->nr_cpus_allowed == 1 || migration_disabled)
+ cpu = select_cpu_restricted(p, prev_cpu, allowed, flags,
+ migration_disabled);
+ else
cpu = scx_select_cpu_dfl(p, prev_cpu, wake_flags,
allowed ?: p->cpus_ptr, flags);
- }
if (we_locked)
raw_spin_unlock_irqrestore(&p->pi_lock, irq_flags);
diff --git a/kernel/sched/ext/internal.h b/kernel/sched/ext/internal.h
index c2ccd268f1c0..e1afcd4e043f 100644
--- a/kernel/sched/ext/internal.h
+++ b/kernel/sched/ext/internal.h
@@ -2255,7 +2255,7 @@ void scx_kick_cpu(struct scx_sched *sch, s32 cpu, u64 flags);
u64 __scx_bpf_now(struct rq *rq);
void schedule_dsq_reenq(struct scx_sched *sch, struct scx_dispatch_q *dsq,
u64 reenq_flags, struct rq *locked_rq);
-void scx_reenq_wait_dispatching(struct task_struct *p);
+void scx_wait_dispatching(struct task_struct *p);
int __scx_init_task(struct scx_sched *sch, struct task_struct *p,
struct cgroup *cgrp, bool fork);
void scx_enable_task(struct scx_sched *sch, struct task_struct *p);