From: Tejun Heo <tj@kernel.org>
To: David Vernet <void@manifault.com>,
Andrea Righi <arighi@nvidia.com>,
Changwoo Min <changwoo@igalia.com>
Cc: sched-ext@lists.linux.dev, Emil Tsalapatis <emil@etsalapatis.com>,
linux-kernel@vger.kernel.org, Tejun Heo <tj@kernel.org>
Subject: [PATCH sched_ext/for-7.3 13/32] sched_ext: Add scx_cmask_ref for validated arena cmask access
Date: Thu, 2 Jul 2026 22:01:40 -1000 [thread overview]
Message-ID: <20260703080159.2314350-14-tj@kernel.org> (raw)
In-Reply-To: <20260703080159.2314350-1-tj@kernel.org>
kfuncs taking struct scx_cmask * from BPF arena memory have two problems.
The pointer can be any value the BPF prog hands in, and the header (@base
and @nr_cids) can be mutated by the prog concurrently with kernel access.
Add scx_cmask_ref, a validated handle. _init() normalizes the input pointer
into the arena's kern_vm range via scx_arena_to_kaddr() and snapshots
@base/@nr_cids; downstream sizing uses the snapshot, not the live header.
_shard() reads slices, _or() / _copy() write back; all bounded by the
snapshot. No callers yet.
Signed-off-by: Tejun Heo <tj@kernel.org>
---
kernel/sched/ext/cid.c | 130 +++++++++++++++++++++++++++++++++++++++
kernel/sched/ext/cid.h | 7 +++
kernel/sched/ext/types.h | 37 +++++++++++
3 files changed, 174 insertions(+)
diff --git a/kernel/sched/ext/cid.c b/kernel/sched/ext/cid.c
index bd0467e8a8d2..7325ad04c386 100644
--- a/kernel/sched/ext/cid.c
+++ b/kernel/sched/ext/cid.c
@@ -633,6 +633,12 @@ enum cmask_op2 {
/* predicates - short-circuit when the per-word result is true */
CMASK_OP2_SUBSET,
CMASK_OP2_INTERSECTS,
+ /*
+ * @a is a BPF-arena cmask. Words on @a use READ_ONCE/WRITE_ONCE since
+ * BPF may read/write concurrently. See scx_cmask_ref_or() / _copy().
+ */
+ CMASK_OP2_REF_OR,
+ CMASK_OP2_REF_COPY,
};
static __always_inline bool cmask_op2_is_pred(const enum cmask_op2 op)
@@ -661,6 +667,12 @@ static __always_inline bool cmask_word_op2(u64 *av, const u64 *bp, u64 mask,
return (READ_ONCE(*bp) & ~READ_ONCE(*av)) & mask;
case CMASK_OP2_INTERSECTS:
return (READ_ONCE(*av) & READ_ONCE(*bp)) & mask;
+ case CMASK_OP2_REF_OR:
+ WRITE_ONCE(*av, READ_ONCE(*av) | (READ_ONCE(*bp) & mask));
+ return false;
+ case CMASK_OP2_REF_COPY:
+ WRITE_ONCE(*av, (READ_ONCE(*av) & ~mask) | (READ_ONCE(*bp) & mask));
+ return false;
}
unreachable();
}
@@ -891,6 +903,124 @@ static const struct btf_kfunc_id_set scx_kfunc_set_cid = {
.set = &scx_kfunc_ids_cid,
};
+/**
+ * scx_cmask_ref_init - Bind a scx_cmask_ref to a BPF-arena cmask
+ * @sch: scheduler whose arena hosts @src
+ * @src: BPF-supplied cmask pointer
+ * @ref: output ref
+ *
+ * Snapshot @src's @base and @nr_cids. The snapshot is necessary because BPF may
+ * mutate the live header asynchronously.
+ *
+ * Return 0 on success, -EINVAL if the snapshotted header is malformed.
+ */
+int scx_cmask_ref_init(struct scx_sched *sch, const struct scx_cmask *src,
+ struct scx_cmask_ref *ref)
+{
+ struct scx_cmask *kern_src = scx_arena_to_kaddr(sch, src);
+ u32 base, nr_cids, npossible = num_possible_cpus();
+
+ base = READ_ONCE(kern_src->base);
+ nr_cids = READ_ONCE(kern_src->nr_cids);
+
+ if (unlikely(base >= npossible || nr_cids > npossible - base))
+ return -EINVAL;
+
+ ref->sch = sch;
+ ref->src = kern_src;
+ ref->base = base;
+ ref->nr_cids = nr_cids;
+
+ ref->shard_first = scx_cid_to_shard[base];
+ if (likely(nr_cids))
+ ref->shard_end = scx_cid_to_shard[base + nr_cids - 1] + 1;
+ else
+ ref->shard_end = ref->shard_first;
+
+ return 0;
+}
+
+/**
+ * scx_cmask_ref_shard - Read one shard from @ref into @out
+ * @ref: validated ref
+ * @shard_idx: target shard, in [@ref->shard_first, @ref->shard_end)
+ * @out: output cmask whose @out->alloc_words must hold the shard
+ *
+ * Set @out to the intersection of @ref's range with @shard_idx's cid range,
+ * with bits[] read from @ref->src via READ_ONCE. Empty intersection sets
+ * @out->nr_cids to 0. scx_error()s on @ref's sched if @out can't hold the
+ * shard.
+ */
+void scx_cmask_ref_shard(const struct scx_cmask_ref *ref, s32 shard_idx,
+ struct scx_cmask *out)
+{
+ const struct scx_cid_shard *shard = &scx_cid_shard_ranges[shard_idx];
+ u32 shard_base = shard->base_cid;
+ u32 shard_end = shard_base + shard->nr_cids;
+ u32 isect_base, isect_end, nr_words, src_off, wi;
+ u64 head_mask, tail_mask;
+
+ isect_base = max(ref->base, shard_base);
+ isect_end = min(ref->base + ref->nr_cids, shard_end);
+
+ if (isect_base >= isect_end) {
+ out->base = shard_base;
+ out->nr_cids = 0;
+ return;
+ }
+
+ nr_words = ((isect_end - 1) / 64) - (isect_base / 64) + 1;
+ if (nr_words > out->alloc_words) {
+ scx_error(ref->sch, "scx_cmask_ref_shard: out alloc_words=%u < %u for shard %d",
+ out->alloc_words, nr_words, shard_idx);
+ out->base = shard_base;
+ out->nr_cids = 0;
+ return;
+ }
+
+ out->base = isect_base;
+ out->nr_cids = isect_end - isect_base;
+ src_off = (isect_base / 64) - (ref->base / 64);
+
+ for (wi = 0; wi < nr_words; wi++)
+ out->bits[wi] = READ_ONCE(ref->src->bits[src_off + wi]);
+
+ head_mask = GENMASK_U64(63, isect_base & 63);
+ out->bits[0] &= head_mask;
+ tail_mask = GENMASK_U64((isect_end - 1) & 63, 0);
+ out->bits[nr_words - 1] &= tail_mask;
+}
+
+/**
+ * scx_cmask_ref_or - OR @src into the arena cmask referenced by @ref
+ * @ref: validated ref
+ * @src: stable kernel cmask
+ *
+ * Bits inside the intersection of @ref's snapshotted range with @src's range
+ * are OR'd into @ref->src and bits outside are left unchanged. Stores on
+ * @ref->src use WRITE_ONCE since BPF may read/write concurrently.
+ */
+void scx_cmask_ref_or(const struct scx_cmask_ref *ref, const struct scx_cmask *src)
+{
+ cmask_walk_op2(ref->src->bits, ref->base, ref->nr_cids,
+ src->bits, src->base, src->nr_cids, CMASK_OP2_REF_OR);
+}
+
+/**
+ * scx_cmask_ref_copy - Copy @src into the arena cmask referenced by @ref
+ * @ref: validated ref
+ * @src: stable kernel cmask
+ *
+ * Bits inside the intersection of @ref's snapshotted range with @src's range
+ * take @src's values and bits outside are left unchanged. Stores on @ref->src
+ * use WRITE_ONCE since BPF may read/write concurrently.
+ */
+void scx_cmask_ref_copy(const struct scx_cmask_ref *ref, const struct scx_cmask *src)
+{
+ cmask_walk_op2(ref->src->bits, ref->base, ref->nr_cids,
+ src->bits, src->base, src->nr_cids, CMASK_OP2_REF_COPY);
+}
+
int scx_cid_kfunc_init(void)
{
return register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS, &scx_kfunc_set_init_cids) ?:
diff --git a/kernel/sched/ext/cid.h b/kernel/sched/ext/cid.h
index cdc18a7a48f5..70d97acd0ac4 100644
--- a/kernel/sched/ext/cid.h
+++ b/kernel/sched/ext/cid.h
@@ -293,4 +293,11 @@ static inline s32 scx_cpu_ret(struct scx_sched *sch, s32 cpu_or_cid)
return scx_cid_to_cpu(sch, cpu_or_cid);
}
+int scx_cmask_ref_init(struct scx_sched *sch, const struct scx_cmask *src,
+ struct scx_cmask_ref *ref);
+void scx_cmask_ref_shard(const struct scx_cmask_ref *ref, s32 shard_idx,
+ struct scx_cmask *out);
+void scx_cmask_ref_or(const struct scx_cmask_ref *ref, const struct scx_cmask *src);
+void scx_cmask_ref_copy(const struct scx_cmask_ref *ref, const struct scx_cmask *src);
+
#endif /* _KERNEL_SCHED_EXT_CID_H */
diff --git a/kernel/sched/ext/types.h b/kernel/sched/ext/types.h
index b31d12931999..98a6e072c33e 100644
--- a/kernel/sched/ext/types.h
+++ b/kernel/sched/ext/types.h
@@ -172,4 +172,41 @@ struct scx_cmask {
#define SCX_CMASK_DEFINE_SHARD(NAME, BASE, NR_CIDS) \
__SCX_CMASK_DEFINE(NAME, BASE, NR_CIDS, SCX_CID_SHARD_MAX_CPUS)
+/*
+ * scx_cmask_ref: validated reference to a BPF-arena cmask.
+ *
+ * scx_cmask_ref_init() normalizes the pointer into the arena and snapshots
+ * @base/@nr_cids. The snapshot is what downstream code uses for sizing - the
+ * live header can be mutated concurrently by BPF.
+ *
+ * scx_cmask_ref_shard() reads one shard into a cmask. scx_cmask_ref_or() and
+ * scx_cmask_ref_copy() write back into the referenced arena cmask, bounded by
+ * the snapshot.
+ *
+ * Typical input use:
+ *
+ * struct scx_cmask_ref ref;
+ * SCX_CMASK_DEFINE(shard, 0, SCX_CID_SHARD_MAX_CPUS);
+ * s32 idx, ret;
+ *
+ * ret = scx_cmask_ref_init(sch, src, &ref);
+ * if (ret < 0)
+ * return ret;
+ *
+ * for (idx = ref.shard_first; idx < ref.shard_end; idx++) {
+ * scx_cmask_ref_shard(&ref, idx, shard);
+ * if (!shard->nr_cids)
+ * continue;
+ * ... use idx and shard ...
+ * }
+ */
+struct scx_cmask_ref {
+ struct scx_sched *sch;
+ struct scx_cmask *src;
+ u32 base;
+ u32 nr_cids;
+ s32 shard_first;
+ s32 shard_end;
+};
+
#endif /* _KERNEL_SCHED_EXT_TYPES_H */
--
2.54.0
next prev parent reply other threads:[~2026-07-03 8:02 UTC|newest]
Thread overview: 61+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-03 8:01 [PATCHSET sched_ext/for-7.3] sched_ext: Capability-based CPU delegation for sub-schedulers Tejun Heo
2026-07-03 8:01 ` [PATCH sched_ext/for-7.3 01/32] sched_ext: Fix premature ops->priv publication in scx_alloc_and_add_sched() Tejun Heo
2026-07-03 8:01 ` [PATCH sched_ext/for-7.3 02/32] tools/sched_ext: scx - Fix cmask_subset(), cmask_equal() and cmask_weight() Tejun Heo
2026-07-03 8:01 ` [PATCH sched_ext/for-7.3 03/32] sched_ext: Use READ_ONCE/WRITE_ONCE in cmask word ops and drop _RACY variants Tejun Heo
2026-07-03 8:33 ` sashiko-bot
2026-07-04 0:54 ` Tejun Heo
2026-07-03 8:01 ` [PATCH sched_ext/for-7.3 04/32] tools/sched_ext: scx_qmap - Use bare u64/u32/s32 integer types Tejun Heo
2026-07-03 8:01 ` [PATCH sched_ext/for-7.3 05/32] sched_ext: Reject direct slice and dsq_vtime writes for cid-form schedulers Tejun Heo
2026-07-03 8:01 ` [PATCH sched_ext/for-7.3 06/32] sched_ext: Make scx_bpf_kick_cid() return void Tejun Heo
2026-07-03 8:01 ` [PATCH sched_ext/for-7.3 07/32] sched_ext: Make the kick machinery per-sched Tejun Heo
2026-07-03 9:02 ` sashiko-bot
2026-07-04 0:54 ` Tejun Heo
2026-07-03 8:01 ` [PATCH sched_ext/for-7.3 08/32] sched_ext: Add ops.init_cids() to finalize the cid layout before init Tejun Heo
2026-07-03 8:01 ` [PATCH sched_ext/for-7.3 09/32] sched_ext: Add CID sharding Tejun Heo
2026-07-03 8:01 ` [PATCH sched_ext/for-7.3 10/32] sched_ext: Add shard boundaries to scx_bpf_cid_override() Tejun Heo
2026-07-03 9:51 ` sashiko-bot
2026-07-04 0:54 ` Tejun Heo
2026-07-03 8:01 ` [PATCH sched_ext/for-7.3 11/32] sched_ext: Defer scx_sched kobj sysfs add into the enable workfns Tejun Heo
2026-07-03 8:01 ` [PATCH sched_ext/for-7.3 12/32] sched_ext: Add per-shard scx_sched storage scaffolding Tejun Heo
2026-07-03 8:01 ` Tejun Heo [this message]
2026-07-03 8:01 ` [PATCH sched_ext/for-7.3 14/32] sched_ext: RCU-protect the sub-sched tree's children/sibling lists Tejun Heo
2026-07-03 10:49 ` sashiko-bot
2026-07-04 0:54 ` Tejun Heo
2026-07-03 8:01 ` [PATCH sched_ext/for-7.3 15/32] sched_ext: Add scx_skip_subtree_pre() Tejun Heo
2026-07-03 8:01 ` [PATCH sched_ext/for-7.3 16/32] sched_ext: Add per-shard cap delegation for sub-schedulers Tejun Heo
2026-07-03 11:17 ` sashiko-bot
2026-07-04 0:54 ` Tejun Heo
2026-07-03 8:01 ` [PATCH sched_ext/for-7.3 17/32] sched_ext: Add coalescing sub_caps_updated() notifier " Tejun Heo
2026-07-03 8:01 ` [PATCH sched_ext/for-7.3 18/32] sched_ext: Maintain per-cpu effective cap copies for single-read checks Tejun Heo
2026-07-03 12:05 ` sashiko-bot
2026-07-04 0:54 ` Tejun Heo
2026-07-03 8:01 ` [PATCH sched_ext/for-7.3 19/32] sched_ext: Add sub_ecaps_updated() effective-cap change notifier Tejun Heo
2026-07-03 12:25 ` sashiko-bot
2026-07-04 0:54 ` Tejun Heo
2026-07-03 8:01 ` [PATCH sched_ext/for-7.3 20/32] sched_ext: Generalize local-DSQ handling to rq-owned DSQs Tejun Heo
2026-07-03 8:01 ` [PATCH sched_ext/for-7.3 21/32] sched_ext: Add reject DSQ for cap-rejected dispatches Tejun Heo
2026-07-03 12:57 ` sashiko-bot
2026-07-04 0:54 ` Tejun Heo
2026-07-03 8:01 ` [PATCH sched_ext/for-7.3 22/32] sched_ext: Add the SCX_CAP_ENQ_IMMED cap Tejun Heo
2026-07-03 8:01 ` [PATCH sched_ext/for-7.3 23/32] sched_ext: Assign a unique id to each scheduler instance Tejun Heo
2026-07-03 8:01 ` [PATCH sched_ext/for-7.3 24/32] sched_ext: Route task slice writes through set_task_slice() Tejun Heo
2026-07-03 8:01 ` [PATCH sched_ext/for-7.3 25/32] sched_ext: Tie cpu occupancy to SCX_CAP_BASE through the task slice Tejun Heo
2026-07-03 13:34 ` sashiko-bot
2026-07-04 0:54 ` Tejun Heo
2026-07-03 8:01 ` [PATCH sched_ext/for-7.3 26/32] sched_ext: Add the SCX_CAP_ENQ cap Tejun Heo
2026-07-03 8:01 ` [PATCH sched_ext/for-7.3 27/32] sched_ext: Gate kicks on SCX_CAP_BASE and preemption on SCX_CAP_PREEMPT Tejun Heo
2026-07-03 14:01 ` sashiko-bot
2026-07-04 0:54 ` Tejun Heo
2026-07-03 8:01 ` [PATCH sched_ext/for-7.3 28/32] sched_ext: Route ops.update_idle() to sub-schedulers and re-notify owed scheds Tejun Heo
2026-07-03 14:14 ` sashiko-bot
2026-07-04 0:54 ` Tejun Heo
2026-07-03 8:01 ` [PATCH sched_ext/for-7.3 29/32] sched_ext: Replay ecaps notifications suppressed by bypass Tejun Heo
2026-07-03 14:28 ` sashiko-bot
2026-07-04 0:54 ` Tejun Heo
2026-07-03 8:01 ` [PATCH sched_ext/for-7.3 30/32] sched_ext: Add scx_bpf_sub_kill() to evict a child sub-scheduler Tejun Heo
2026-07-03 14:45 ` sashiko-bot
2026-07-04 0:54 ` Tejun Heo
2026-07-03 8:01 ` [PATCH sched_ext/for-7.3 31/32] tools/sched_ext: scx_qmap - Expand hierarchical sub-scheduling Tejun Heo
2026-07-03 14:57 ` sashiko-bot
2026-07-04 0:54 ` Tejun Heo
2026-07-03 8:01 ` [PATCH sched_ext/for-7.3 32/32] tools/sched_ext: scx_qmap - Add sub-sched cap fault injection Tejun Heo
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260703080159.2314350-14-tj@kernel.org \
--to=tj@kernel.org \
--cc=arighi@nvidia.com \
--cc=changwoo@igalia.com \
--cc=emil@etsalapatis.com \
--cc=linux-kernel@vger.kernel.org \
--cc=sched-ext@lists.linux.dev \
--cc=void@manifault.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.