| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * A cid-form scheduler checking the cmask cid-form ops.enable() receives: the | |
| * header, every cid bit against p->cpus_ptr, and that set_cmask() follows with | |
| * the same mask before set_weight() and before the task first becomes runnable, | |
| * and never runs before enable(). | |
| * | |
| * Copyright (c) 2026 Tejun Heo <tj@kernel.org> | |
| */ | |
| #include <scx/common.bpf.h> | |
| char _license[] SEC("license") = "GPL"; | |
| struct { | |
| __uint(type, BPF_MAP_TYPE_ARENA); | |
| __uint(map_flags, BPF_F_MMAPABLE); | |
| __uint(max_entries, 1 << 16); | |
| } arena SEC(".maps"); | |
| struct task_ctx { | |
| u64 enable_fp; /* fingerprint of the mask enable() received */ | |
| bool enabled; | |
| bool pending; /* enable() ran, the initial set_cmask() hasn't */ | |
| }; | |
| struct { | |
| __uint(type, BPF_MAP_TYPE_TASK_STORAGE); | |
| __uint(map_flags, BPF_F_NO_PREALLOC); | |
| __type(key, int); | |
| __type(value, struct task_ctx); | |
| } task_ctx_stor SEC(".maps"); | |
| /* details of a cid bit mismatch, filled by check_mask() */ | |
| struct mask_mismatch { | |
| s32 cid; | |
| bool want; | |
| bool got; | |
| }; | |
| u64 nr_enable, nr_initial_set_cmask, nr_set_cmask, nr_set_weight; | |
| UEI_DEFINE(uei); | |
| static struct task_ctx *lookup_task_ctx(struct task_struct *p) | |
| { | |
| struct task_ctx *tctx; | |
| tctx = bpf_task_storage_get(&task_ctx_stor, p, 0, 0); | |
| if (!tctx) | |
| scx_bpf_error("task_ctx lookup failed for %s[%d]", p->comm, p->pid); | |
| return tctx; | |
| } | |
| /* | |
| * Verify @m's header and every cid bit against @p's cpumask and fingerprint the | |
| * bits into @fp. Return 0 on success, -EINVAL on a bad header, -ENOENT on a cid | |
| * without a cpu and -EIO on a bit mismatch with the details in @mm. | |
| */ | |
| static int check_mask(struct task_struct *p, const struct scx_cmask __arena *m, u64 *fp, | |
| struct mask_mismatch *mm) | |
| { | |
| u32 nr_cids = scx_bpf_nr_cids(); | |
| u64 h = 0; | |
| s32 cid; | |
| if (m->base || m->nr_cids != nr_cids || m->alloc_words != CMASK_NR_WORDS(nr_cids)) | |
| return -EINVAL; | |
| bpf_for(cid, 0, nr_cids) { | |
| bool want, got; | |
| s32 cpu; | |
| cpu = scx_bpf_cid_to_cpu(cid); | |
| if (cpu < 0) | |
| return -ENOENT; | |
| want = bpf_cpumask_test_cpu(cpu, p->cpus_ptr); | |
| got = cmask_test(cid, m); | |
| if (want != got) { | |
| mm->cid = cid; | |
| mm->want = want; | |
| mm->got = got; | |
| return -EIO; | |
| } | |
| h = h * 31 + got; | |
| } | |
| *fp = h; | |
| return 0; | |
| } | |
| s32 BPF_STRUCT_OPS_SLEEPABLE(enable_cmask_init_task, struct task_struct *p, | |
| struct scx_init_task_args *args) | |
| { | |
| if (!bpf_task_storage_get(&task_ctx_stor, p, 0, BPF_LOCAL_STORAGE_GET_F_CREATE)) | |
| return -ENOMEM; | |
| return 0; | |
| } | |
| void BPF_STRUCT_OPS(enable_cmask_enable, struct task_struct *p, struct scx_enable_args *args) | |
| { | |
| struct scx_cmask __arena *m = (struct scx_cmask __arena *)args->cmask_arena_addr; | |
| struct mask_mismatch mm = {}; | |
| struct task_ctx *tctx; | |
| int ret; | |
| asm volatile("" :: "r"(&arena)); | |
| tctx = lookup_task_ctx(p); | |
| if (!tctx) | |
| return; | |
| __sync_fetch_and_add(&nr_enable, 1); | |
| if (tctx->enabled || tctx->pending) { | |
| scx_bpf_error("enable: %s[%d] enabled twice", p->comm, p->pid); | |
| return; | |
| } | |
| ret = check_mask(p, m, &tctx->enable_fp, &mm); | |
| if (ret) { | |
| scx_bpf_error("enable: %s[%d] cmask check failed %d cid=%d want=%d got=%d", | |
| p->comm, p->pid, ret, mm.cid, mm.want, mm.got); | |
| return; | |
| } | |
| tctx->enabled = true; | |
| tctx->pending = true; | |
| } | |
| void BPF_STRUCT_OPS(enable_cmask_set_cmask, struct task_struct *p, | |
| struct scx_cmask __arena *m) | |
| { | |
| struct mask_mismatch mm = {}; | |
| struct task_ctx *tctx; | |
| u64 fp; | |
| int ret; | |
| asm volatile("" :: "r"(&arena)); | |
| tctx = lookup_task_ctx(p); | |
| if (!tctx) | |
| return; | |
| __sync_fetch_and_add(&nr_set_cmask, 1); | |
| if (!tctx->enabled) { | |
| scx_bpf_error("set_cmask: %s[%d] not enabled", p->comm, p->pid); | |
| return; | |
| } | |
| ret = check_mask(p, m, &fp, &mm); | |
| if (ret) { | |
| scx_bpf_error("set_cmask: %s[%d] cmask check failed %d cid=%d want=%d got=%d", | |
| p->comm, p->pid, ret, mm.cid, mm.want, mm.got); | |
| return; | |
| } | |
| if (tctx->pending) { | |
| if (fp != tctx->enable_fp) { | |
| scx_bpf_error("set_cmask: %s[%d] initial mask differs from enable()", | |
| p->comm, p->pid); | |
| return; | |
| } | |
| tctx->pending = false; | |
| __sync_fetch_and_add(&nr_initial_set_cmask, 1); | |
| } | |
| } | |
| void BPF_STRUCT_OPS(enable_cmask_set_weight, struct task_struct *p, u32 weight) | |
| { | |
| struct task_ctx *tctx; | |
| tctx = lookup_task_ctx(p); | |
| if (!tctx) | |
| return; | |
| __sync_fetch_and_add(&nr_set_weight, 1); | |
| if (tctx->pending) | |
| scx_bpf_error("set_weight: %s[%d] before the initial set_cmask()", p->comm, | |
| p->pid); | |
| } | |
| void BPF_STRUCT_OPS(enable_cmask_runnable, struct task_struct *p, u64 enq_flags) | |
| { | |
| struct task_ctx *tctx; | |
| tctx = lookup_task_ctx(p); | |
| if (!tctx) | |
| return; | |
| if (tctx->pending) | |
| scx_bpf_error("runnable: %s[%d] before the initial set_cmask()", p->comm, | |
| p->pid); | |
| } | |
| void BPF_STRUCT_OPS(enable_cmask_disable, struct task_struct *p) | |
| { | |
| struct task_ctx *tctx; | |
| tctx = lookup_task_ctx(p); | |
| if (!tctx) | |
| return; | |
| tctx->enabled = false; | |
| tctx->pending = false; | |
| } | |
| void BPF_STRUCT_OPS(enable_cmask_exit, struct scx_exit_info *ei) | |
| { | |
| UEI_RECORD(uei, ei); | |
| } | |
| SCX_OPS_CID_DEFINE(enable_cmask_ops, | |
| .init_task = (void *)enable_cmask_init_task, | |
| .enable = (void *)enable_cmask_enable, | |
| .set_cmask = (void *)enable_cmask_set_cmask, | |
| .set_weight = (void *)enable_cmask_set_weight, | |
| .runnable = (void *)enable_cmask_runnable, | |
| .disable = (void *)enable_cmask_disable, | |
| .exit = (void *)enable_cmask_exit, | |
| .flags = SCX_OPS_SWITCH_PARTIAL, | |
| .name = "enable_cmask"); |