* [PATCH 1/2] compat.bpf.h: Gate scx_bpf_dsq_peek kfunc behind kernel version 7.1.0
2026-08-17 14:31 [PATCH 0/2] sched_ext: sync compat.bpf.h peek/reenqueue fixes from sched-ext/scx Changwoo Min
@ 2026-08-17 14:31 ` Changwoo Min
2026-08-17 17:41 ` Tejun Heo
2026-08-17 14:31 ` [PATCH 2/2] compat.bpf.h: add scx_bpf_reenqueue_local_from_anywhere() compat helper Changwoo Min
1 sibling, 1 reply; 5+ messages in thread
From: Changwoo Min @ 2026-08-17 14:31 UTC (permalink / raw)
To: tj, void, arighi, changwoo; +Cc: gavinguo, kernel-dev, sched-ext, linux-kernel
From: Gavin Guo <gavinguo@igalia.com>
__COMPAT_scx_bpf_dsq_peek() selects the lockless kfunc whenever the
symbol resolves in the kernel BTF. However, its lockless implementation
could return a stale task_struct pointer. The stale pointer issues are
resolved only after v7.1 kernel with the following patches:
commit 2f2ea7709266 ("sched_ext: Use dsq->first_task instead of list_empty() in dispatch_enqueue() FIFO-tail")
commit 71d7847cad44 ("sched_ext: Fix scx_bpf_dsq_peek() with FIFO DSQs")
Require bpf_ksym_exists(scx_bpf_dsq_peek) AND LINUX_KERNEL_VERSION >=
KERNEL_VERSION(7, 1, 0) before calling the kfunc to mitigate the issue;
otherwise fall through to the existing bpf_iter_scx_dsq path instead.
See also the lavd patch, working around the bug by avoiding calling
the kfunc when unnecessary and having more context explanation:
ac863374ce4f ("scx_lavd: Gate dsq_peek_task_load behind no-fast-lb")
Signed-off-by: Gavin Guo <gavinguo@igalia.com>
Signed-off-by: Changwoo Min <changwoo@igalia.com>
---
tools/sched_ext/include/scx/compat.bpf.h | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/tools/sched_ext/include/scx/compat.bpf.h b/tools/sched_ext/include/scx/compat.bpf.h
index 3ab642f92c8a..03976f5851d9 100644
--- a/tools/sched_ext/include/scx/compat.bpf.h
+++ b/tools/sched_ext/include/scx/compat.bpf.h
@@ -92,15 +92,20 @@ int bpf_cpumask_populate(struct bpf_cpumask *dst, void *src, size_t src__sz) __k
/*
* v6.19: Introduce lockless peek API for user DSQs.
+ * v7.1: Resolve the stale pointer issue of the lockless peek API.
+ *
+ * The kfunc exists on earlier kernels but its lockless implementation could
+ * return stale task pointers. Require kernel version >= 7.1.0 before calling
+ * it; otherwise fall through to the bpf_iter_scx_dsq fallback below.
*
- * Preserve the following macro until v6.21.
*/
static inline struct task_struct *__COMPAT_scx_bpf_dsq_peek(u64 dsq_id)
{
struct task_struct *p = NULL;
struct bpf_iter_scx_dsq it;
- if (bpf_ksym_exists(scx_bpf_dsq_peek))
+ if (bpf_ksym_exists(scx_bpf_dsq_peek) &&
+ LINUX_KERNEL_VERSION >= KERNEL_VERSION(7, 1, 0))
return scx_bpf_dsq_peek(dsq_id);
if (!bpf_iter_scx_dsq_new(&it, dsq_id, 0))
p = bpf_iter_scx_dsq_next(&it);
--
2.55.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH 2/2] compat.bpf.h: add scx_bpf_reenqueue_local_from_anywhere() compat helper
2026-08-17 14:31 [PATCH 0/2] sched_ext: sync compat.bpf.h peek/reenqueue fixes from sched-ext/scx Changwoo Min
2026-08-17 14:31 ` [PATCH 1/2] compat.bpf.h: Gate scx_bpf_dsq_peek kfunc behind kernel version 7.1.0 Changwoo Min
@ 2026-08-17 14:31 ` Changwoo Min
2026-08-17 17:41 ` Tejun Heo
1 sibling, 1 reply; 5+ messages in thread
From: Changwoo Min @ 2026-08-17 14:31 UTC (permalink / raw)
To: tj, void, arighi, changwoo; +Cc: gavinguo, kernel-dev, sched-ext, linux-kernel
scx_bpf_reenqueue_local()'s generic compat wrapper inlines a v1 fallback
that is only callable from ops.cpu_release, and veristat rejects it on
kernels without v2. Callers draining a local DSQ from an arbitrary
context (e.g. a tracepoint) must gate on a call-from-anywhere kfunc
directly.
Add scx_bpf_reenqueue_local_from_anywhere() to compat.bpf.h to
encapsulate that: call a call-from-anywhere kfunc when present and return
0, else return -ENOTSUP so the caller knows the drain did not run. Two
kfuncs qualify -- the v7.1 generic scx_bpf_dsq_reenq(), which will
eventually deprecate scx_bpf_reenqueue_local(), and the v6.19 v2
reenqueue-local variant. Prefer the generic one; v1 cannot be called from
anywhere, so it maps to -ENOTSUP. scx_bpf_reenqueue_local() itself grows
the same generic-first preference, and the scx_bpf_dsq_reenq___compat
declaration and __COMPAT_has_generic_reenq() helper move above the v6.19
block so both wrappers can use them.
Test each ksym in its own branch: ORing two bpf_ksym_exists() checks
folds into a bitwise OR of the two weak ksym addresses, which the
verifier rejects.
No functional change intended.
Suggested-by: Andrea Righi <arighi@nvidia.com>
Signed-off-by: Changwoo Min <changwoo@igalia.com>
Signed-off-by: Tejun Heo <tj@kernel.org>
---
tools/sched_ext/include/scx/compat.bpf.h | 39 ++++++++++++++++++------
1 file changed, 30 insertions(+), 9 deletions(-)
diff --git a/tools/sched_ext/include/scx/compat.bpf.h b/tools/sched_ext/include/scx/compat.bpf.h
index 03976f5851d9..55f118e40545 100644
--- a/tools/sched_ext/include/scx/compat.bpf.h
+++ b/tools/sched_ext/include/scx/compat.bpf.h
@@ -383,6 +383,17 @@ static inline void scx_bpf_task_set_dsq_vtime(struct task_struct *p, u64 vtime)
p->scx.dsq_vtime = vtime;
}
+/*
+ * v7.1: New scx_bpf_dsq_reenq() that allows re-enqueues on more DSQs. This
+ * will eventually deprecate scx_bpf_reenqueue_local().
+ */
+void scx_bpf_dsq_reenq___compat(u64 dsq_id, u64 reenq_flags) __ksym __weak;
+
+static inline bool __COMPAT_has_generic_reenq(void)
+{
+ return bpf_ksym_exists(scx_bpf_dsq_reenq___compat);
+}
+
/*
* v6.19: The new void variant can be called from anywhere while the older v1
* variant can only be called from ops.cpu_release(). The double ___ prefixes on
@@ -400,21 +411,31 @@ static inline bool __COMPAT_scx_bpf_reenqueue_local_from_anywhere(void)
static inline void scx_bpf_reenqueue_local(void)
{
- if (__COMPAT_scx_bpf_reenqueue_local_from_anywhere())
+ if (__COMPAT_has_generic_reenq())
+ scx_bpf_dsq_reenq___compat(SCX_DSQ_LOCAL, 0);
+ else if (__COMPAT_scx_bpf_reenqueue_local_from_anywhere())
scx_bpf_reenqueue_local___v2___compat();
else
scx_bpf_reenqueue_local___v1();
}
-/*
- * v7.1: New scx_bpf_dsq_reenq() that allows re-enqueues on more DSQs. This
- * will eventually deprecate scx_bpf_reenqueue_local().
- */
-void scx_bpf_dsq_reenq___compat(u64 dsq_id, u64 reenq_flags) __ksym __weak;
-
-static inline bool __COMPAT_has_generic_reenq(void)
+static inline int scx_bpf_reenqueue_local_from_anywhere(void)
{
- return bpf_ksym_exists(scx_bpf_dsq_reenq___compat);
+ /*
+ * The generic reenq kfunc and the v2 reenqueue-local variant can both be
+ * called from anywhere; v1 cannot. Test each ksym in its own branch with a
+ * distinct call: combining them with || would fold into a bitwise OR of the
+ * two ksym addresses, which the verifier rejects.
+ */
+ if (__COMPAT_has_generic_reenq()) {
+ scx_bpf_dsq_reenq___compat(SCX_DSQ_LOCAL, 0);
+ return 0;
+ }
+ if (__COMPAT_scx_bpf_reenqueue_local_from_anywhere()) {
+ scx_bpf_reenqueue_local___v2___compat();
+ return 0;
+ }
+ return -ENOTSUP;
}
static inline void scx_bpf_dsq_reenq(u64 dsq_id, u64 reenq_flags)
--
2.55.0
^ permalink raw reply related [flat|nested] 5+ messages in thread