#include <scx/common.bpf.h>
#define SHARED_DSQ 0
struct {
__uint(type, BPF_MAP_TYPE_QUEUE);
__uint(max_entries, 32768);
__type(value, s32);
} global_queue SEC(".maps");
char _license[] SEC("license") = "GPL";
UEI_DEFINE(uei);
u64 enqueue_cnt, dequeue_cnt, dispatch_dequeue_cnt, change_dequeue_cnt, bpf_queue_full;
u32 test_scenario;
enum task_state {
TASK_NONE = 0,
TASK_ENQUEUED,
TASK_DISPATCHED,
};
struct task_ctx {
enum task_state state;
u64 enqueue_seq;
};
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");
static struct task_ctx *try_lookup_task_ctx(struct task_struct *p)
{
return bpf_task_storage_get(&task_ctx_stor, p, 0, 0);
}
s32 BPF_STRUCT_OPS(dequeue_select_cpu, struct task_struct *p,
s32 prev_cpu, u64 wake_flags)
{
struct task_ctx *tctx;
tctx = try_lookup_task_ctx(p);
if (!tctx)
return prev_cpu;
switch (test_scenario) {
case 0:
scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL, SCX_SLICE_DFL, 0);
tctx->state = TASK_DISPATCHED;
break;
case 1:
scx_bpf_dsq_insert(p, SCX_DSQ_GLOBAL, SCX_SLICE_DFL, 0);
tctx->state = TASK_DISPATCHED;
break;
case 2:
if (tctx->state == TASK_ENQUEUED)
scx_bpf_error("%d (%s): enqueue while in ENQUEUED state seq=%llu",
p->pid, p->comm, tctx->enqueue_seq);
scx_bpf_dsq_insert(p, SHARED_DSQ, SCX_SLICE_DFL, 0);
__sync_fetch_and_add(&enqueue_cnt, 1);
tctx->state = TASK_ENQUEUED;
tctx->enqueue_seq++;
break;
}
return prev_cpu;
}
void BPF_STRUCT_OPS(dequeue_enqueue, struct task_struct *p, u64 enq_flags)
{
struct task_ctx *tctx;
s32 pid = p->pid;
tctx = try_lookup_task_ctx(p);
if (!tctx)
return;
switch (test_scenario) {
case 3:
scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL, SCX_SLICE_DFL, enq_flags);
tctx->state = TASK_DISPATCHED;
break;
case 4:
scx_bpf_dsq_insert(p, SCX_DSQ_GLOBAL, SCX_SLICE_DFL, enq_flags);
tctx->state = TASK_DISPATCHED;
break;
case 5:
if (tctx->state == TASK_ENQUEUED)
scx_bpf_error("%d (%s): enqueue while in ENQUEUED state seq=%llu",
p->pid, p->comm, tctx->enqueue_seq);
scx_bpf_dsq_insert(p, SHARED_DSQ, SCX_SLICE_DFL, enq_flags);
__sync_fetch_and_add(&enqueue_cnt, 1);
tctx->state = TASK_ENQUEUED;
tctx->enqueue_seq++;
break;
case 6:
if (tctx->state == TASK_ENQUEUED)
scx_bpf_error("%d (%s): enqueue while in ENQUEUED state seq=%llu",
p->pid, p->comm, tctx->enqueue_seq);
if (bpf_map_push_elem(&global_queue, &pid, 0)) {
scx_bpf_dsq_insert(p, SCX_DSQ_GLOBAL, SCX_SLICE_DFL, enq_flags);
__sync_fetch_and_add(&bpf_queue_full, 1);
tctx->state = TASK_DISPATCHED;
} else {
__sync_fetch_and_add(&enqueue_cnt, 1);
tctx->state = TASK_ENQUEUED;
tctx->enqueue_seq++;
}
break;
default:
scx_bpf_dsq_insert(p, SCX_DSQ_GLOBAL, SCX_SLICE_DFL, enq_flags);
tctx->state = TASK_DISPATCHED;
break;
}
scx_bpf_kick_cpu(scx_bpf_task_cpu(p), SCX_KICK_IDLE);
}
void BPF_STRUCT_OPS(dequeue_dequeue, struct task_struct *p, u64 deq_flags)
{
struct task_ctx *tctx;
__sync_fetch_and_add(&dequeue_cnt, 1);
tctx = try_lookup_task_ctx(p);
if (!tctx)
return;
if (test_scenario == 0 || test_scenario == 3) {
scx_bpf_error("%d (%s): dequeue called for local DSQ scenario",
p->pid, p->comm);
return;
}
if (test_scenario == 1 || test_scenario == 4) {
scx_bpf_error("%d (%s): dequeue called for global DSQ scenario",
p->pid, p->comm);
return;
}
if (deq_flags & SCX_DEQ_SCHED_CHANGE) {
__sync_fetch_and_add(&change_dequeue_cnt, 1);
if (tctx->state != TASK_ENQUEUED && tctx->state != TASK_DISPATCHED)
scx_bpf_error("%d (%s): invalid property change dequeue state=%d seq=%llu",
p->pid, p->comm, tctx->state, tctx->enqueue_seq);
tctx->state = TASK_NONE;
} else {
__sync_fetch_and_add(&dispatch_dequeue_cnt, 1);
if (tctx->state != TASK_ENQUEUED && tctx->state != TASK_NONE)
scx_bpf_error("%d (%s): dispatch dequeue from state %d seq=%llu",
p->pid, p->comm, tctx->state, tctx->enqueue_seq);
if (tctx->state == TASK_ENQUEUED)
tctx->state = TASK_DISPATCHED;
}
}
void BPF_STRUCT_OPS(dequeue_dispatch, s32 cpu, struct task_struct *prev)
{
if (test_scenario == 6) {
struct task_ctx *tctx;
struct task_struct *p;
s32 pid;
if (bpf_map_pop_elem(&global_queue, &pid))
return;
p = bpf_task_from_pid(pid);
if (!p)
return;
tctx = try_lookup_task_ctx(p);
if (!tctx || tctx->state == TASK_NONE) {
bpf_task_release(p);
return;
}
if (bpf_cpumask_test_cpu(cpu, p->cpus_ptr))
scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL_ON | cpu, SCX_SLICE_DFL, 0);
else
scx_bpf_dsq_insert(p, SCX_DSQ_GLOBAL, SCX_SLICE_DFL, 0);
bpf_task_release(p);
} else {
scx_bpf_dsq_move_to_local(SHARED_DSQ, 0);
}
}
s32 BPF_STRUCT_OPS(dequeue_init_task, struct task_struct *p,
struct scx_init_task_args *args)
{
struct task_ctx *tctx;
tctx = bpf_task_storage_get(&task_ctx_stor, p, 0,
BPF_LOCAL_STORAGE_GET_F_CREATE);
if (!tctx)
return -ENOMEM;
return 0;
}
s32 BPF_STRUCT_OPS_SLEEPABLE(dequeue_init)
{
s32 ret;
ret = scx_bpf_create_dsq(SHARED_DSQ, -1);
if (ret)
return ret;
return 0;
}
void BPF_STRUCT_OPS(dequeue_exit, struct scx_exit_info *ei)
{
UEI_RECORD(uei, ei);
}
SEC(".struct_ops.link")
struct sched_ext_ops dequeue_ops = {
.select_cpu = (void *)dequeue_select_cpu,
.enqueue = (void *)dequeue_enqueue,
.dequeue = (void *)dequeue_dequeue,
.dispatch = (void *)dequeue_dispatch,
.init_task = (void *)dequeue_init_task,
.init = (void *)dequeue_init,
.exit = (void *)dequeue_exit,
.flags = SCX_OPS_ENQ_LAST,
.name = "dequeue_test",
};