1 /* SPDX-License-Identifier: GPL-2.0 */
2 /*
3 * scx_qmap: a demonstration and testing scheduler for sched_ext features.
4 *
5 * A simple scheduler that exercises a broad set of sched_ext features. Unlikely
6 * to be useful for real workloads. It demonstrates:
7 *
8 * - BPF-side queueing using TIDs.
9 * - BPF arena for scheduler state.
10 * - Core-sched support.
11 * - Hierarchical sub-scheduling: delegating cpus to child cgroup schedulers.
12 *
13 * Base design: Five FIFOs (arena-backed doubly-linked lists through per-task
14 * context). A task is assigned to a FIFO by its compound weight. Each cpu
15 * round-robins the FIFOs, dispatching more from higher ones.
16 *
17 * Sub-scheduling: Any qmap sched can delegate cpus to its own child cgroup
18 * schedulers and keep the rest for its tasks. Terminology:
19 *
20 * excl - A cpu the delegatee owns wholly (ENQ_IMMED|ENQ|PREEMPT).
21 * shared - A cpu delegated as ENQ_IMMED only. Time-shared.
22 * held_excl / held_shared - What this node was handed by its parent.
23 * held-excl cpus are re-delegatable. A held-shared cpu is a
24 * time-share that stays self-local.
25 * self - The excl cpus the node kept for itself, plus all of held_shared.
26 * owner - Who holds a cid - a child slot, CID_SELF, or CID_NONE.
27 *
28 * The scheduler splits its held-excl cpus among self and the children in
29 * proportion to each node's cpu.weight, handing each the floor of its share as
30 * excl cpus. The leftover from rounding forms a shared pool the round-robin
31 * timer hands around. With no excl cpu to delegate, the node evicts its
32 * children.
33 *
34 * This policy is a demonstration only, not a practical one. The split
35 * considers only direct children and is not work-conserving. It only exists to
36 * drive sub-sched primitives with as simple logic as possible.
37 *
38 * Copyright (c) 2022 Meta Platforms, Inc. and affiliates.
39 * Copyright (c) 2022 Tejun Heo <tj@kernel.org>
40 * Copyright (c) 2022 David Vernet <dvernet@meta.com>
41 */
42 #include <scx/common.bpf.h>
43
44 #include "scx_qmap.h"
45
46 enum consts {
47 ONE_SEC_IN_NS = 1000000000,
48 ONE_MSEC_IN_NS = 1000000,
49 LOWPRI_INTV_NS = 10 * ONE_MSEC_IN_NS,
50 SHARED_DSQ = 0,
51 HIGHPRI_DSQ = 1,
52 LOWPRI_DSQ = 2,
53 HIGHPRI_WEIGHT = 8668, /* this is what -20 maps to */
54 };
55
56 char _license[] SEC("license") = "GPL";
57
58 const volatile u64 slice_ns;
59 const volatile u32 stall_user_nth;
60 const volatile u32 stall_kernel_nth;
61 const volatile u32 dsp_inf_loop_after;
62 const volatile u32 dsp_batch;
63 const volatile bool highpri_boosting;
64 const volatile bool print_dsqs_and_events;
65 const volatile bool print_msgs;
66 const volatile u64 sub_cgroup_id;
67 const volatile s32 disallow_tgid;
68 const volatile bool suppress_dump;
69 const volatile u32 immed_stress_nth;
70 const volatile u32 max_tasks;
71
72 /* sub-sched: period for handing the round-robin cid pool to the next child */
73 const volatile u64 round_robin_ns;
74
75 /*
76 * Optional cid-override test harness. When cid_override_mode is non-zero,
77 * qmap_init_cids() calls scx_bpf_cid_override() with the caller-supplied arrays
78 * to exercise the kfunc's acceptance and error paths. See enum
79 * qmap_cid_override for the modes.
80 */
81 const volatile u32 cid_override_mode;
82 const volatile u32 cid_override_nr_shards;
83
84 UEI_DEFINE(uei);
85
86 /*
87 * All scheduler state - per-cpu context, stats counters, core-sched sequence
88 * numbers, sub-sched cgroup ids - lives in this single BPF arena map. Userspace
89 * reaches it via skel->arena->qa.
90 */
91 struct {
92 __uint(type, BPF_MAP_TYPE_ARENA);
93 __uint(map_flags, BPF_F_MMAPABLE);
94 __uint(max_entries, 1 << 16); /* upper bound in pages */
95 #if defined(__TARGET_ARCH_arm64) || defined(__aarch64__)
96 __ulong(map_extra, 0x1ull << 32); /* user/BPF mmap base */
97 #else
98 __ulong(map_extra, 0x1ull << 44);
99 #endif
100 } arena SEC(".maps");
101
102 struct qmap_arena __arena_global qa;
103
104 /* ensure that BPF and userspace are seeing the same size for qmap_cmask */
105 _Static_assert(QMAP_CMASK_WORDS == CMASK_NR_WORDS(SCX_QMAP_MAX_CPUS),
106 "QMAP_CMASK_WORDS must equal CMASK_NR_WORDS(SCX_QMAP_MAX_CPUS)");
107 _Static_assert(sizeof(struct qmap_cmask) ==
108 struct_size_t(struct scx_cmask, bits, QMAP_CMASK_WORDS),
109 "qmap_cmask must be exactly sized to back a full scx_cmask");
110
111 /* Per-queue locks. Each in its own .data section as bpf_res_spin_lock requires. */
112 __hidden struct bpf_res_spin_lock qa_q_lock0 SEC(".data.qa_q_lock0");
113 __hidden struct bpf_res_spin_lock qa_q_lock1 SEC(".data.qa_q_lock1");
114 __hidden struct bpf_res_spin_lock qa_q_lock2 SEC(".data.qa_q_lock2");
115 __hidden struct bpf_res_spin_lock qa_q_lock3 SEC(".data.qa_q_lock3");
116 __hidden struct bpf_res_spin_lock qa_q_lock4 SEC(".data.qa_q_lock4");
117
qa_q_lock(s32 qid)118 static struct bpf_res_spin_lock *qa_q_lock(s32 qid)
119 {
120 switch (qid) {
121 case 0: return &qa_q_lock0;
122 case 1: return &qa_q_lock1;
123 case 2: return &qa_q_lock2;
124 case 3: return &qa_q_lock3;
125 case 4: return &qa_q_lock4;
126 default: return NULL;
127 }
128 }
129
130 /*
131 * If enabled, CPU performance target is set according to the queue index
132 * according to the following table.
133 */
134 static const u32 qidx_to_cpuperf_target[] = {
135 [0] = SCX_CPUPERF_ONE * 0 / 4,
136 [1] = SCX_CPUPERF_ONE * 1 / 4,
137 [2] = SCX_CPUPERF_ONE * 2 / 4,
138 [3] = SCX_CPUPERF_ONE * 3 / 4,
139 [4] = SCX_CPUPERF_ONE * 4 / 4,
140 };
141
142 /*
143 * Per-queue sequence numbers to implement core-sched ordering.
144 *
145 * Tail seq is assigned to each queued task and incremented. Head seq tracks the
146 * sequence number of the latest dispatched task. The distance between the a
147 * task's seq and the associated queue's head seq is called the queue distance
148 * and used when comparing two tasks for ordering. See qmap_core_sched_before().
149 */
150
151 /*
152 * Per-task scheduling context. Allocated from the qa.task_ctxs[] slab in
153 * arena. While the task is alive the entry is referenced from task_ctx_stor;
154 * while it's free the entry sits on the free list singly-linked through
155 * @next_free.
156 *
157 * When the task is queued on one of the five priority FIFOs, @q_idx is the
158 * queue index and @q_next/@q_prev link it in the queue's doubly-linked list.
159 * @q_idx is -1 when the task isn't on any queue.
160 */
161 struct task_ctx {
162 struct task_ctx __arena *next_free; /* only valid on free list */
163 struct task_ctx __arena *q_next; /* queue link, NULL if tail */
164 struct task_ctx __arena *q_prev; /* queue link, NULL if head */
165 struct qmap_fifo __arena *fifo; /* queue we're on, NULL if not queued */
166 u64 tid;
167 s32 pid; /* for dump only */
168 bool force_local; /* Dispatch directly to local_dsq */
169 bool highpri;
170 u64 core_sched_seq;
171 struct scx_cmask cpus_allowed; /* per-task affinity in cid space */
172 };
173
174 /*
175 * Slab stride for task_ctx. cpus_allowed's flex array bits[] overlaps the
176 * tail bytes appended per entry; struct_size() gives the actual per-entry
177 * footprint.
178 */
179 #define TASK_CTX_STRIDE \
180 struct_size_t(struct task_ctx, cpus_allowed.bits, \
181 CMASK_NR_WORDS(SCX_QMAP_MAX_CPUS))
182
183 /* All task_ctx pointers are arena pointers. */
184 typedef struct task_ctx __arena task_ctx_t;
185
186 /* Holds an arena pointer to the task's slab entry. */
187 struct task_ctx_stor_val {
188 task_ctx_t *taskc;
189 };
190
191 struct {
192 __uint(type, BPF_MAP_TYPE_TASK_STORAGE);
193 __uint(map_flags, BPF_F_NO_PREALLOC);
194 __type(key, int);
195 __type(value, struct task_ctx_stor_val);
196 } task_ctx_stor SEC(".maps");
197
198 /* Protects the task_ctx slab free list. */
199 __hidden struct bpf_res_spin_lock qa_task_lock SEC(".data.qa_task_lock");
200
qmap_spin_lock(struct bpf_res_spin_lock * lock)201 static int qmap_spin_lock(struct bpf_res_spin_lock *lock)
202 {
203 if (bpf_res_spin_lock(lock)) {
204 scx_bpf_error("res_spin_lock failed");
205 return -EBUSY;
206 }
207 return 0;
208 }
209
210 /*
211 * Try prev_cid, then scan cpus_allowed AND idle_cids AND self_cids round-robin
212 * from prev_cid + 1. Atomic claim retries on race; bounded by
213 * IDLE_PICK_RETRIES to keep the verifier's insn budget in check.
214 */
215 #define IDLE_PICK_RETRIES 16
216
pick_direct_dispatch_cid(struct task_struct * p,s32 prev_cid,task_ctx_t * taskc)217 static s32 pick_direct_dispatch_cid(struct task_struct *p, s32 prev_cid,
218 task_ctx_t *taskc)
219 {
220 u32 nr_cids = scx_bpf_nr_cids();
221 s32 cid;
222 u32 i;
223
224 if (cmask_test(prev_cid, &qa.self_cids.mask) &&
225 cmask_test_and_clear(prev_cid, &qa.idle_cids.mask))
226 return prev_cid;
227
228 cid = prev_cid;
229 bpf_for(i, 0, IDLE_PICK_RETRIES) {
230 cid = cmask_next_and2_set_wrap(&taskc->cpus_allowed,
231 &qa.idle_cids.mask,
232 &qa.self_cids.mask, cid + 1);
233 barrier_var(cid);
234 if (cid >= nr_cids)
235 return -1;
236 if (cmask_test_and_clear(cid, &qa.idle_cids.mask))
237 return cid;
238 }
239 return -1;
240 }
241
242 /*
243 * Force a reference to the arena map. The verifier associates an arena with
244 * a program by finding an LD_IMM64 instruction that loads the arena's BPF
245 * map; programs that only use arena pointers returned from task-local
246 * storage (like qmap_select_cpu) never reference @arena directly. Without
247 * this, the verifier rejects addr_space_cast with "addr_space_cast insn
248 * can only be used in a program that has an associated arena".
249 */
250 #define QMAP_TOUCH_ARENA() do { asm volatile("" :: "r"(&arena)); } while (0)
251
lookup_task_ctx(struct task_struct * p)252 static task_ctx_t *lookup_task_ctx(struct task_struct *p)
253 {
254 struct task_ctx_stor_val *v;
255
256 QMAP_TOUCH_ARENA();
257
258 v = bpf_task_storage_get(&task_ctx_stor, p, 0, 0);
259 if (!v || !v->taskc)
260 return NULL;
261 return v->taskc;
262 }
263
264 /* Append @taskc to the tail of @fifo. Must not already be queued. */
qmap_fifo_enqueue(struct qmap_fifo __arena * fifo,task_ctx_t * taskc)265 static void qmap_fifo_enqueue(struct qmap_fifo __arena *fifo, task_ctx_t *taskc)
266 {
267 struct bpf_res_spin_lock *lock = qa_q_lock(fifo->idx);
268
269 if (!lock || qmap_spin_lock(lock))
270 return;
271 taskc->fifo = fifo;
272 taskc->q_next = NULL;
273 taskc->q_prev = fifo->tail;
274 if (fifo->tail)
275 fifo->tail->q_next = taskc;
276 else
277 fifo->head = taskc;
278 fifo->tail = taskc;
279 bpf_res_spin_unlock(lock);
280 }
281
282 /* Pop the head of @fifo. Returns NULL if empty. */
qmap_fifo_pop(struct qmap_fifo __arena * fifo)283 static task_ctx_t *qmap_fifo_pop(struct qmap_fifo __arena *fifo)
284 {
285 struct bpf_res_spin_lock *lock = qa_q_lock(fifo->idx);
286 task_ctx_t *taskc;
287
288 if (!lock || qmap_spin_lock(lock))
289 return NULL;
290 taskc = fifo->head;
291 if (taskc) {
292 fifo->head = taskc->q_next;
293 if (taskc->q_next)
294 taskc->q_next->q_prev = NULL;
295 else
296 fifo->tail = NULL;
297 taskc->q_next = NULL;
298 taskc->q_prev = NULL;
299 taskc->fifo = NULL;
300 }
301 bpf_res_spin_unlock(lock);
302 return taskc;
303 }
304
305 /* Remove @taskc from its fifo. No-op if not queued. */
qmap_fifo_remove(task_ctx_t * taskc)306 static void qmap_fifo_remove(task_ctx_t *taskc)
307 {
308 struct qmap_fifo __arena *fifo = taskc->fifo;
309 struct bpf_res_spin_lock *lock;
310
311 if (!fifo)
312 return;
313
314 lock = qa_q_lock(fifo->idx);
315 if (!lock || qmap_spin_lock(lock))
316 return;
317
318 /* Re-check under lock — a concurrent pop may have cleared fifo. */
319 if (taskc->fifo != fifo) {
320 bpf_res_spin_unlock(lock);
321 return;
322 }
323
324 if (taskc->q_next)
325 taskc->q_next->q_prev = taskc->q_prev;
326 else
327 fifo->tail = taskc->q_prev;
328 if (taskc->q_prev)
329 taskc->q_prev->q_next = taskc->q_next;
330 else
331 fifo->head = taskc->q_next;
332 taskc->q_next = NULL;
333 taskc->q_prev = NULL;
334 taskc->fifo = NULL;
335 bpf_res_spin_unlock(lock);
336 }
337
BPF_STRUCT_OPS(qmap_select_cid,struct task_struct * p,s32 prev_cid,u64 wake_flags)338 s32 BPF_STRUCT_OPS(qmap_select_cid, struct task_struct *p,
339 s32 prev_cid, u64 wake_flags)
340 {
341 task_ctx_t *taskc;
342 s32 cid;
343
344 if (!(taskc = lookup_task_ctx(p)))
345 return prev_cid;
346
347 if (p->scx.weight < 2 && !(p->flags & PF_KTHREAD))
348 return prev_cid;
349
350 cid = pick_direct_dispatch_cid(p, prev_cid, taskc);
351
352 if (cid >= 0) {
353 taskc->force_local = true;
354 return cid;
355 } else {
356 return prev_cid;
357 }
358 }
359
360 /*
361 * A received time-shared cid is held ENQ_IMMED-only, so inserts must set
362 * SCX_ENQ_IMMED.
363 */
needs_immed(s32 cid)364 static u64 needs_immed(s32 cid)
365 {
366 return qa.cid_shared[cid] ? SCX_ENQ_IMMED : 0;
367 }
368
369 /* first cid this node does NOT hold for fault injection, -1 if none */
first_unavail_cid(void)370 static s32 first_unavail_cid(void)
371 {
372 s32 nr_cids = qa.nr_cids, c;
373
374 if (nr_cids > SCX_QMAP_MAX_CPUS) {
375 scx_bpf_error("-ERANGE");
376 return -1;
377 }
378
379 bpf_for(c, 0, nr_cids) {
380 if (!cmask_test(c, &qa.held_excl.mask) &&
381 !cmask_test(c, &qa.held_shared.mask))
382 return c;
383 }
384 return -1;
385 }
386
weight_to_idx(u32 weight)387 static int weight_to_idx(u32 weight)
388 {
389 /* Coarsely map the compound weight to a FIFO. */
390 if (weight <= 25)
391 return 0;
392 else if (weight <= 50)
393 return 1;
394 else if (weight < 200)
395 return 2;
396 else if (weight < 400)
397 return 3;
398 else
399 return 4;
400 }
401
BPF_STRUCT_OPS(qmap_enqueue,struct task_struct * p,u64 enq_flags)402 void BPF_STRUCT_OPS(qmap_enqueue, struct task_struct *p, u64 enq_flags)
403 {
404 static u32 user_cnt, kernel_cnt;
405 task_ctx_t *taskc;
406 int idx = weight_to_idx(p->scx.weight);
407 s32 cid;
408
409 if (enq_flags & SCX_ENQ_REENQ) {
410 u64 reason = p->scx.flags & SCX_TASK_REENQ_REASON_MASK;
411
412 __sync_fetch_and_add(&qa.nr_reenqueued, 1);
413 if (scx_bpf_task_cid(p) == 0)
414 __sync_fetch_and_add(&qa.nr_reenqueued_cid0, 1);
415 /* cap-loss and IMMED-handback bounces, relocated below */
416 if (reason == SCX_TASK_REENQ_CAP)
417 __sync_fetch_and_add(&qa.nr_reenq_cap, 1);
418 else if (reason == SCX_TASK_REENQ_IMMED)
419 __sync_fetch_and_add(&qa.nr_reenq_immed, 1);
420 }
421
422 if (p->flags & PF_KTHREAD) {
423 if (stall_kernel_nth && !(++kernel_cnt % stall_kernel_nth))
424 return;
425 } else {
426 if (stall_user_nth && !(++user_cnt % stall_user_nth))
427 return;
428 }
429
430 if (qa.test_error_cnt && !--qa.test_error_cnt)
431 scx_bpf_error("test triggering error");
432
433 if (!(taskc = lookup_task_ctx(p)))
434 return;
435
436 /*
437 * All enqueued tasks must have their core_sched_seq updated for correct
438 * core-sched ordering. Also, take a look at the end of qmap_dispatch().
439 */
440 taskc->core_sched_seq = qa.core_sched_tail_seqs[idx]++;
441
442 /*
443 * A task of ours that can run on none of our self cids - the parent
444 * didn't grant them or we delegated them to children - would starve in
445 * SHARED/FIFO since we only pull from those on self cids.
446 *
447 * Force it onto its first allowed cid's local DSQ. If we hold that cid
448 * it runs. Otherwise the insert carries SCX_ENQ_RESCUE and the kernel
449 * diverts the task to its rescue path.
450 */
451 if (!cmask_intersects(&taskc->cpus_allowed, &qa.self_cids.mask)) {
452 s32 c = cmask_next_set_wrap(&taskc->cpus_allowed, 0);
453
454 if (c >= 0 && c < scx_bpf_nr_cids()) {
455 taskc->force_local = false;
456 __sync_fetch_and_add(&qa.nr_rescue_dsp, 1);
457 scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL_ON | c, slice_ns,
458 enq_flags | needs_immed(c) | SCX_ENQ_RESCUE);
459 return;
460 }
461 }
462
463 /*
464 * Fault injection: deliberately dispatch one of our own tasks to a cid
465 * we don't hold. The inserts carry SCX_ENQ_RESCUE and divert to the
466 * kernel rescue path, a deterministic rescue-traffic generator. Under
467 * -B 0 the kernel cap check rejects and re-enqueues them instead, so
468 * nr_inject_attempts tracks nr_reenq_cap 1:1 and proves delivery-time
469 * enforcement. Throttled.
470 */
471 if (qa.inject_mode == QMAP_INJ_WRONG_CID && p->nr_cpus_allowed > 1 &&
472 !(enq_flags & SCX_ENQ_REENQ)) {
473 static u32 inj_cnt;
474
475 if (!(++inj_cnt % 64)) {
476 s32 bad = first_unavail_cid();
477
478 if (bad >= 0 && cmask_test(bad, &taskc->cpus_allowed)) {
479 __sync_fetch_and_add(&qa.nr_inject_attempts, 1);
480 __sync_fetch_and_add(&qa.nr_rescue_dsp, 1);
481 scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL_ON | bad, slice_ns,
482 enq_flags | SCX_ENQ_RESCUE);
483 return;
484 }
485 }
486 }
487
488 /*
489 * IMMED stress testing: Every immed_stress_nth'th enqueue, dispatch
490 * directly to prev_cpu's local DSQ even when busy to force dsq->nr > 1
491 * and exercise the kernel IMMED reenqueue trigger paths.
492 */
493 if (immed_stress_nth && !(enq_flags & SCX_ENQ_REENQ)) {
494 static u32 immed_stress_cnt;
495
496 if (!(++immed_stress_cnt % immed_stress_nth)) {
497 taskc->force_local = false;
498 scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL_ON | scx_bpf_task_cid(p),
499 slice_ns, enq_flags);
500 return;
501 }
502 }
503
504 /*
505 * If qmap_select_cid() is telling us to or this is the last runnable
506 * task on the CPU, enqueue locally.
507 */
508 if (taskc->force_local) {
509 taskc->force_local = false;
510 scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL, slice_ns,
511 enq_flags | needs_immed(scx_bpf_task_cid(p)));
512 return;
513 }
514
515 /* see lowpri_timerfn() */
516 if (__COMPAT_has_generic_reenq() &&
517 p->scx.weight < 2 && !(p->flags & PF_KTHREAD) && !(enq_flags & SCX_ENQ_REENQ)) {
518 scx_bpf_dsq_insert(p, LOWPRI_DSQ, slice_ns, enq_flags);
519 return;
520 }
521
522 /* if select_cid() wasn't called, try direct dispatch */
523 if (!__COMPAT_is_enq_cpu_selected(enq_flags) &&
524 (cid = pick_direct_dispatch_cid(p, scx_bpf_task_cid(p), taskc)) >= 0) {
525 __sync_fetch_and_add(&qa.nr_ddsp_from_enq, 1);
526 scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL_ON | cid, slice_ns,
527 enq_flags | needs_immed(cid));
528 return;
529 }
530
531 /*
532 * If the task was re-enqueued due to the CPU being preempted by a
533 * higher priority scheduling class, just re-enqueue the task directly
534 * on the global DSQ. As we want another CPU to pick it up, find and
535 * kick an idle cid.
536 */
537 if (enq_flags & SCX_ENQ_REENQ) {
538 s32 cid;
539
540 scx_bpf_dsq_insert(p, SHARED_DSQ, 0, enq_flags);
541 cid = cmask_next_and2_set_wrap(&taskc->cpus_allowed,
542 &qa.idle_cids.mask,
543 &qa.self_cids.mask, 0);
544 if (cid < scx_bpf_nr_cids())
545 scx_bpf_kick_cid(cid, SCX_KICK_IDLE);
546 return;
547 }
548
549 /* Queue on the selected FIFO. */
550 qmap_fifo_enqueue(&qa.fifos[idx], taskc);
551
552 if (highpri_boosting && p->scx.weight >= HIGHPRI_WEIGHT) {
553 taskc->highpri = true;
554 __sync_fetch_and_add(&qa.nr_highpri_queued, 1);
555 }
556 __sync_fetch_and_add(&qa.nr_enqueued, 1);
557 }
558
BPF_STRUCT_OPS(qmap_dequeue,struct task_struct * p,u64 deq_flags)559 void BPF_STRUCT_OPS(qmap_dequeue, struct task_struct *p, u64 deq_flags)
560 {
561 task_ctx_t *taskc;
562
563 __sync_fetch_and_add(&qa.nr_dequeued, 1);
564 if (deq_flags & SCX_DEQ_CORE_SCHED_EXEC)
565 __sync_fetch_and_add(&qa.nr_core_sched_execed, 1);
566
567 taskc = lookup_task_ctx(p);
568 if (taskc && taskc->fifo) {
569 if (taskc->highpri)
570 __sync_fetch_and_sub(&qa.nr_highpri_queued, 1);
571 qmap_fifo_remove(taskc);
572 }
573 }
574
update_core_sched_head_seq(struct task_struct * p)575 static void update_core_sched_head_seq(struct task_struct *p)
576 {
577 int idx = weight_to_idx(p->scx.weight);
578 task_ctx_t *taskc;
579
580 if ((taskc = lookup_task_ctx(p)))
581 qa.core_sched_head_seqs[idx] = taskc->core_sched_seq;
582 }
583
584 /*
585 * One pass over SHARED_DSQ: rescue stranded tasks and boost highpri ones. A
586 * task whose cids were lost while it was queued in the fifos would strand on
587 * SHARED_DSQ, which is consumed only on self cids it can't run on - move it to
588 * the kernel rescue path. One whose cids were lost after the highpri cull is
589 * likewise rescued out of HIGHPRI_DSQ below.
590 *
591 * To demonstrate the use of scx_bpf_dsq_move(), implement silly selective
592 * priority boosting mechanism by moving highpri tasks to HIGHPRI_DSQ and then
593 * consuming them first. This makes minor difference only when dsp_batch is
594 * larger than 1.
595 *
596 * scx_bpf_dsq_move[_vtime]() are allowed both from ops.dispatch() and
597 * non-rq-lock holding BPF programs. As demonstration, this function is called
598 * from qmap_dispatch() and monitor_timerfn().
599 */
scan_shared_dsq(bool from_timer)600 static bool scan_shared_dsq(bool from_timer)
601 {
602 struct task_struct *p;
603 s32 this_cid = scx_bpf_this_cid();
604 u32 nr_cids = scx_bpf_nr_cids();
605
606 /* rescue strands and move highpri tasks to HIGHPRI_DSQ */
607 bpf_for_each(scx_dsq, p, SHARED_DSQ, 0) {
608 static u64 highpri_seq;
609 task_ctx_t *taskc;
610 s32 c;
611
612 if (!(taskc = lookup_task_ctx(p)))
613 return false;
614
615 /* stranded? rescue - it can't be dispatched here either way */
616 if (!cmask_intersects(&taskc->cpus_allowed, &qa.self_cids.mask)) {
617 c = cmask_next_set_wrap(&taskc->cpus_allowed, 0);
618 if (c >= 0 && c < scx_bpf_nr_cids()) {
619 __sync_fetch_and_add(&qa.nr_rescue_dsp, 1);
620 scx_bpf_dsq_move(BPF_FOR_EACH_ITER, p, SCX_DSQ_LOCAL_ON | c,
621 needs_immed(c) | SCX_ENQ_RESCUE);
622 }
623 continue;
624 }
625
626 if (taskc->highpri) {
627 /* exercise the set_*() and vtime interface too */
628 scx_bpf_dsq_move_set_slice(BPF_FOR_EACH_ITER, slice_ns * 2);
629 scx_bpf_dsq_move_set_vtime(BPF_FOR_EACH_ITER, highpri_seq++);
630 scx_bpf_dsq_move_vtime(BPF_FOR_EACH_ITER, p, HIGHPRI_DSQ, 0);
631 }
632 }
633
634 /*
635 * Scan HIGHPRI_DSQ and dispatch until a task that can run here is
636 * found. Prefer this_cid if the task allows it; otherwise RR-scan the
637 * task's cpus_allowed starting after this_cid.
638 */
639 bpf_for_each(scx_dsq, p, HIGHPRI_DSQ, 0) {
640 task_ctx_t *taskc;
641 bool dispatched = false;
642 s32 cid;
643
644 if (!(taskc = lookup_task_ctx(p)))
645 return false;
646
647 /* only run highpri tasks on cids this node holds, not delegated ones */
648 if (cmask_test(this_cid, &taskc->cpus_allowed) &&
649 cmask_test(this_cid, &qa.self_cids.mask))
650 cid = this_cid;
651 else
652 cid = cmask_next_and_set_wrap(&taskc->cpus_allowed,
653 &qa.self_cids.mask,
654 this_cid + 1);
655 if (cid >= nr_cids) {
656 /* stranded after the cull - rescue it from here */
657 s32 c = cmask_next_set_wrap(&taskc->cpus_allowed, 0);
658
659 if (c >= 0 && c < nr_cids) {
660 __sync_fetch_and_add(&qa.nr_rescue_dsp, 1);
661 scx_bpf_dsq_move(BPF_FOR_EACH_ITER, p, SCX_DSQ_LOCAL_ON | c,
662 needs_immed(c) | SCX_ENQ_RESCUE);
663 }
664 continue;
665 }
666
667 if (scx_bpf_dsq_move(BPF_FOR_EACH_ITER, p, SCX_DSQ_LOCAL_ON | cid,
668 SCX_ENQ_PREEMPT | needs_immed(cid))) {
669 if (cid == this_cid) {
670 dispatched = true;
671 __sync_fetch_and_add(&qa.nr_expedited_local, 1);
672 } else {
673 __sync_fetch_and_add(&qa.nr_expedited_remote, 1);
674 }
675 if (from_timer)
676 __sync_fetch_and_add(&qa.nr_expedited_from_timer, 1);
677 } else {
678 __sync_fetch_and_add(&qa.nr_expedited_lost, 1);
679 }
680
681 if (dispatched)
682 return true;
683 }
684
685 return false;
686 }
687
BPF_STRUCT_OPS(qmap_dispatch,s32 cid,struct task_struct * prev)688 void BPF_STRUCT_OPS(qmap_dispatch, s32 cid, struct task_struct *prev)
689 {
690 struct task_struct *p;
691 struct cpu_ctx __arena *cpuc;
692 task_ctx_t *taskc;
693 u32 batch = dsp_batch ?: 1;
694 s32 owner, i;
695
696 if (scan_shared_dsq(false))
697 return;
698
699 /*
700 * Sub-sched routing: a child-owned cid goes to its owner. Never run
701 * this node's own tasks on a delegated cid. Read without the guard.
702 */
703 owner = qa.part.cid_owner[cid];
704 if (owner == CID_SHARED) {
705 /* route to the live rr holder (0 = self, runs below) */
706 s32 pos = qa.part.rr_pos;
707 u64 holder_cgid = (pos >= 0 && pos < MAX_PARTS) ?
708 qa.part.rr_slots[pos] : 0;
709
710 if (holder_cgid) {
711 scx_bpf_sub_dispatch(holder_cgid);
712 return;
713 }
714 } else if (owner >= 0 && owner < MAX_SUB_SCHEDS) {
715 u64 cgid = qa.sub_sched_ctxs[owner].cgroup_id;
716
717 if (cgid) {
718 if (scx_bpf_sub_dispatch(cgid))
719 __sync_fetch_and_add(&qa.sub_sched_ctxs[owner].nr_dsps, 1);
720 return;
721 }
722 }
723
724 if (!qa.nr_highpri_queued && scx_bpf_dsq_move_to_local(SHARED_DSQ, needs_immed(cid)))
725 return;
726
727 if (dsp_inf_loop_after && qa.nr_dispatched > dsp_inf_loop_after) {
728 /*
729 * PID 2 should be kthreadd which should mostly be idle and off
730 * the scheduler. Let's keep dispatching it to force the kernel
731 * to call this function over and over again.
732 */
733 p = bpf_task_from_pid(2);
734 if (p) {
735 scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL, slice_ns, 0);
736 bpf_task_release(p);
737 return;
738 }
739 }
740
741 cpuc = &qa.cpu_ctxs[scx_bpf_this_cid()];
742
743 for (i = 0; i < 5; i++) {
744 /* Advance the dispatch cursor and pick the fifo. */
745 if (!cpuc->dsp_cnt) {
746 cpuc->dsp_idx = (cpuc->dsp_idx + 1) % 5;
747 cpuc->dsp_cnt = 1 << cpuc->dsp_idx;
748 }
749
750 /* Dispatch or advance. */
751 bpf_repeat(BPF_MAX_LOOPS) {
752 task_ctx_t *taskc;
753
754 taskc = qmap_fifo_pop(&qa.fifos[cpuc->dsp_idx]);
755 if (!taskc)
756 break;
757
758 p = scx_bpf_tid_to_task(taskc->tid);
759 if (!p)
760 continue;
761
762 if (taskc->highpri)
763 __sync_fetch_and_sub(&qa.nr_highpri_queued, 1);
764
765 update_core_sched_head_seq(p);
766 __sync_fetch_and_add(&qa.nr_dispatched, 1);
767
768 scx_bpf_dsq_insert(p, SHARED_DSQ, slice_ns, 0);
769
770 /*
771 * scx_qmap uses a global BPF queue that any CPU's
772 * dispatch can pop from. If this CPU popped a task that
773 * can't run here, it gets stranded on SHARED_DSQ after
774 * consume_dispatch_q() skips it. Kick the task's home
775 * CPU so it drains SHARED_DSQ.
776 *
777 * There's a race between the pop and the flush of the
778 * buffered dsq_insert:
779 *
780 * CPU 0 (dispatching) CPU 1 (home, idle)
781 * ~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
782 * pop from BPF queue
783 * dsq_insert(buffered)
784 * balance:
785 * SHARED_DSQ empty
786 * BPF queue empty
787 * -> goes idle
788 * flush -> on SHARED
789 * kick CPU 1
790 * wakes, drains task
791 *
792 * The kick prevents indefinite stalls but a per-CPU
793 * kthread like ksoftirqd can be briefly stranded when
794 * its home CPU enters idle with softirq pending,
795 * triggering:
796 *
797 * "NOHZ tick-stop error: local softirq work is pending, handler #N!!!"
798 *
799 * from report_idle_softirq(). The kick lands shortly
800 * after and the home CPU drains the task. This could be
801 * avoided by e.g. dispatching pinned tasks to local or
802 * global DSQs, but the current code is left as-is to
803 * document this class of issue -- other schedulers
804 * seeing similar warnings can use this as a reference.
805 */
806 if (!cmask_test(cid, &taskc->cpus_allowed))
807 scx_bpf_kick_cid(scx_bpf_task_cid(p), 0);
808 batch--;
809 cpuc->dsp_cnt--;
810 if (!batch || !scx_bpf_dispatch_nr_slots()) {
811 if (scan_shared_dsq(false))
812 return;
813 scx_bpf_dsq_move_to_local(SHARED_DSQ, needs_immed(cid));
814 return;
815 }
816 if (!cpuc->dsp_cnt)
817 break;
818 }
819
820 cpuc->dsp_cnt = 0;
821 }
822
823 if (scan_shared_dsq(false))
824 return;
825
826 /*
827 * No other tasks. @prev will keep running. Update its core_sched_seq as
828 * if the task were enqueued and dispatched immediately.
829 */
830 if (prev) {
831 taskc = lookup_task_ctx(prev);
832 if (!taskc)
833 return;
834
835 taskc->core_sched_seq =
836 qa.core_sched_tail_seqs[weight_to_idx(prev->scx.weight)]++;
837 }
838 }
839
BPF_STRUCT_OPS(qmap_tick,struct task_struct * p)840 void BPF_STRUCT_OPS(qmap_tick, struct task_struct *p)
841 {
842 struct cpu_ctx __arena *cpuc = &qa.cpu_ctxs[scx_bpf_this_cid()];
843 int idx;
844
845 /*
846 * Use the running avg of weights to select the target cpuperf level.
847 * This is a demonstration of the cpuperf feature rather than a
848 * practical strategy to regulate CPU frequency.
849 */
850 cpuc->avg_weight = cpuc->avg_weight * 3 / 4 + p->scx.weight / 4;
851 idx = weight_to_idx(cpuc->avg_weight);
852 cpuc->cpuperf_target = qidx_to_cpuperf_target[idx];
853
854 scx_bpf_cidperf_set(scx_bpf_task_cid(p), cpuc->cpuperf_target);
855 }
856
857 /*
858 * The distance from the head of the queue scaled by the weight of the queue.
859 * The lower the number, the older the task and the higher the priority.
860 */
task_qdist(struct task_struct * p,task_ctx_t * taskc)861 static s64 task_qdist(struct task_struct *p, task_ctx_t *taskc)
862 {
863 int idx = weight_to_idx(p->scx.weight);
864 s64 qdist;
865
866 qdist = taskc->core_sched_seq - qa.core_sched_head_seqs[idx];
867
868 /*
869 * As queue index increments, the priority doubles. The queue w/ index 3
870 * is dispatched twice more frequently than 2. Reflect the difference by
871 * scaling qdists accordingly. Note that the shift amount needs to be
872 * flipped depending on the sign to avoid flipping priority direction.
873 */
874 if (qdist >= 0)
875 return qdist << (4 - idx);
876 else
877 return qdist << idx;
878 }
879
880 /*
881 * This is called to determine the task ordering when core-sched is picking
882 * tasks to execute on SMT siblings and should encode about the same ordering as
883 * the regular scheduling path. Use the priority-scaled distances from the head
884 * of the queues to compare the two tasks which should be consistent with the
885 * dispatch path behavior.
886 */
BPF_STRUCT_OPS(qmap_core_sched_before,struct task_struct * a,struct task_struct * b)887 bool BPF_STRUCT_OPS(qmap_core_sched_before,
888 struct task_struct *a, struct task_struct *b)
889 {
890 task_ctx_t *taskc_a = lookup_task_ctx(a);
891 task_ctx_t *taskc_b = lookup_task_ctx(b);
892
893 /*
894 * A task delegated to a sub-scheduler has no task_ctx here. Order such
895 * pairs by the kernel's default ordering - a running task after every
896 * waiting task, then by runnable_at.
897 */
898 if (!taskc_a || !taskc_b) {
899 if (a->on_cpu != b->on_cpu)
900 return b->on_cpu;
901 return time_before(a->scx.runnable_at, b->scx.runnable_at);
902 }
903
904 return task_qdist(a, taskc_a) < task_qdist(b, taskc_b);
905 }
906
907 /*
908 * sched_switch tracepoint and cpu_release handlers are no longer needed.
909 * With SCX_OPS_ALWAYS_ENQ_IMMED, wakeup_preempt_scx() reenqueues IMMED
910 * tasks when a higher-priority scheduling class takes the CPU.
911 */
912
BPF_STRUCT_OPS_SLEEPABLE(qmap_init_task,struct task_struct * p,struct scx_init_task_args * args)913 s32 BPF_STRUCT_OPS_SLEEPABLE(qmap_init_task, struct task_struct *p,
914 struct scx_init_task_args *args)
915 {
916 struct task_ctx_stor_val *v;
917 task_ctx_t *taskc;
918
919 if (qa.inject_mode == QMAP_INJ_INIT_FAIL &&
920 !bpf_strncmp(p->comm, 6, "qmfail"))
921 return -ENOMEM;
922
923 if (p->tgid == disallow_tgid)
924 p->scx.disallow = true;
925
926 /* pop a slab entry off the free list */
927 if (qmap_spin_lock(&qa_task_lock))
928 return -EBUSY;
929 taskc = qa.task_free_head;
930 if (taskc)
931 qa.task_free_head = taskc->next_free;
932 bpf_res_spin_unlock(&qa_task_lock);
933 if (!taskc) {
934 scx_bpf_error("task_ctx slab exhausted (max_tasks=%u)", max_tasks);
935 return -ENOMEM;
936 }
937
938 taskc->next_free = NULL;
939 taskc->q_next = NULL;
940 taskc->q_prev = NULL;
941 taskc->fifo = NULL;
942 taskc->tid = p->scx.tid;
943 taskc->pid = p->pid;
944 taskc->force_local = false;
945 taskc->highpri = false;
946 taskc->core_sched_seq = 0;
947 cmask_init(&taskc->cpus_allowed, 0, scx_bpf_nr_cids());
948 bpf_rcu_read_lock();
949 cmask_from_cpumask(&taskc->cpus_allowed, p->cpus_ptr);
950 bpf_rcu_read_unlock();
951
952 v = bpf_task_storage_get(&task_ctx_stor, p, NULL,
953 BPF_LOCAL_STORAGE_GET_F_CREATE);
954 if (!v) {
955 /* push back to the free list */
956 if (!qmap_spin_lock(&qa_task_lock)) {
957 taskc->next_free = qa.task_free_head;
958 qa.task_free_head = taskc;
959 bpf_res_spin_unlock(&qa_task_lock);
960 }
961 return -ENOMEM;
962 }
963 v->taskc = taskc;
964 return 0;
965 }
966
BPF_STRUCT_OPS(qmap_exit_task,struct task_struct * p,struct scx_exit_task_args * args)967 void BPF_STRUCT_OPS(qmap_exit_task, struct task_struct *p,
968 struct scx_exit_task_args *args)
969 {
970 struct task_ctx_stor_val *v;
971 task_ctx_t *taskc;
972
973 v = bpf_task_storage_get(&task_ctx_stor, p, NULL, 0);
974 if (!v || !v->taskc)
975 return;
976 taskc = v->taskc;
977 v->taskc = NULL;
978
979 if (qmap_spin_lock(&qa_task_lock))
980 return;
981 taskc->next_free = qa.task_free_head;
982 qa.task_free_head = taskc;
983 bpf_res_spin_unlock(&qa_task_lock);
984 }
985
BPF_STRUCT_OPS(qmap_dump,struct scx_dump_ctx * dctx)986 void BPF_STRUCT_OPS(qmap_dump, struct scx_dump_ctx *dctx)
987 {
988 task_ctx_t *taskc;
989 s32 i;
990
991 QMAP_TOUCH_ARENA();
992
993 if (suppress_dump)
994 return;
995
996 /*
997 * Walk the queue lists without locking - kfunc calls (scx_bpf_dump)
998 * aren't in the verifier's kfunc_spin_allowed() list so we can't hold
999 * a lock and dump. Best-effort; racing may print stale tids but the
1000 * walk is bounded by bpf_repeat() so it always terminates.
1001 */
1002 bpf_for(i, 0, 5) {
1003 scx_bpf_dump("QMAP FIFO[%d]:", i);
1004 taskc = qa.fifos[i].head;
1005 bpf_repeat(4096) {
1006 if (!taskc)
1007 break;
1008 scx_bpf_dump(" %d:%llu", taskc->pid, taskc->tid);
1009 taskc = taskc->q_next;
1010 }
1011 scx_bpf_dump("\n");
1012 }
1013 }
1014
BPF_STRUCT_OPS(qmap_dump_cid,struct scx_dump_ctx * dctx,s32 cid,bool idle)1015 void BPF_STRUCT_OPS(qmap_dump_cid, struct scx_dump_ctx *dctx, s32 cid, bool idle)
1016 {
1017 struct cpu_ctx __arena *cpuc = &qa.cpu_ctxs[cid];
1018
1019 if (suppress_dump || idle)
1020 return;
1021
1022 scx_bpf_dump("QMAP: dsp_idx=%llu dsp_cnt=%llu avg_weight=%u cpuperf_target=%u",
1023 cpuc->dsp_idx, cpuc->dsp_cnt, cpuc->avg_weight,
1024 cpuc->cpuperf_target);
1025 }
1026
BPF_STRUCT_OPS(qmap_dump_task,struct scx_dump_ctx * dctx,struct task_struct * p)1027 void BPF_STRUCT_OPS(qmap_dump_task, struct scx_dump_ctx *dctx, struct task_struct *p)
1028 {
1029 struct task_ctx_stor_val *v;
1030 task_ctx_t *taskc;
1031
1032 QMAP_TOUCH_ARENA();
1033
1034 if (suppress_dump)
1035 return;
1036 v = bpf_task_storage_get(&task_ctx_stor, p, NULL, 0);
1037 if (!v || !v->taskc)
1038 return;
1039 taskc = v->taskc;
1040
1041 scx_bpf_dump("QMAP: force_local=%d core_sched_seq=%llu",
1042 taskc->force_local, taskc->core_sched_seq);
1043 }
1044
BPF_STRUCT_OPS(qmap_cpuctl_init,struct cgroup * cgrp,struct scx_cgroup_init_args * args)1045 s32 BPF_STRUCT_OPS(qmap_cpuctl_init, struct cgroup *cgrp, struct scx_cgroup_init_args *args)
1046 {
1047 QMAP_TOUCH_ARENA();
1048
1049 if (print_msgs)
1050 bpf_printk("CGRP INIT %llu weight=%u period=%lu quota=%ld burst=%lu",
1051 cgrp->kn->id, args->weight, args->bw_period_us,
1052 args->bw_quota_us, args->bw_burst_us);
1053
1054 if (qa.inject_mode == QMAP_INJ_CGRP_INIT_FAIL) {
1055 char name[7] = {};
1056
1057 bpf_probe_read_kernel_str(name, sizeof(name), cgrp->kn->name);
1058 if (!bpf_strncmp(name, 6, "qmfail"))
1059 return -ENOMEM;
1060 }
1061
1062 return 0;
1063 }
1064
1065 static void redistribute(void);
1066
BPF_STRUCT_OPS(qmap_cpuctl_set_weight,struct cgroup * cgrp,u32 weight)1067 void BPF_STRUCT_OPS(qmap_cpuctl_set_weight, struct cgroup *cgrp, u32 weight)
1068 {
1069 u64 cgid = cgrp->kn->id;
1070 s32 i;
1071
1072 QMAP_TOUCH_ARENA();
1073
1074 if (print_msgs)
1075 bpf_printk("CGRP SET %llu weight=%u", cgid, weight);
1076
1077 /*
1078 * Knobs belong to the parent, so this op carries the child subs'
1079 * attach point weights. Adjust the matching sub's share of the cid
1080 * partition. Other cgroups don't participate in the split.
1081 */
1082 for (i = 0; i < MAX_SUB_SCHEDS; i++) {
1083 if (qa.sub_sched_ctxs[i].cgroup_id != cgid)
1084 continue;
1085 if (qa.sub_sched_ctxs[i].weight != weight) {
1086 qa.sub_sched_ctxs[i].weight = weight;
1087 redistribute();
1088 }
1089 break;
1090 }
1091 }
1092
BPF_STRUCT_OPS(qmap_cpuctl_set_bandwidth,struct cgroup * cgrp,u64 period_us,u64 quota_us,u64 burst_us)1093 void BPF_STRUCT_OPS(qmap_cpuctl_set_bandwidth, struct cgroup *cgrp, u64 period_us,
1094 u64 quota_us, u64 burst_us)
1095 {
1096 if (print_msgs)
1097 bpf_printk("CGRP SET %llu period=%lu quota=%ld burst=%lu",
1098 cgrp->kn->id, period_us, quota_us, burst_us);
1099 }
1100
BPF_STRUCT_OPS(qmap_cpuctl_move,struct task_struct * p,struct cgroup * from,struct cgroup * to)1101 void BPF_STRUCT_OPS(qmap_cpuctl_move, struct task_struct *p, struct cgroup *from,
1102 struct cgroup *to)
1103 {
1104 if (print_msgs)
1105 bpf_printk("CGRP MOVE %d %llu -> %llu",
1106 p->pid, from->kn->id, to->kn->id);
1107 }
1108
BPF_STRUCT_OPS(qmap_update_idle,s32 cid,bool idle)1109 void BPF_STRUCT_OPS(qmap_update_idle, s32 cid, bool idle)
1110 {
1111 QMAP_TOUCH_ARENA();
1112
1113 /*
1114 * The kernel delivers update_idle() for every cid this node holds
1115 * SCX_CAP_BASE on. Track every cid's idle state regardless of
1116 * delegation: the direct-dispatch pick masks idle_cids with self_cids
1117 * at selection, so a cid already idle when it returns to self needs no
1118 * reseed here.
1119 */
1120 if (idle)
1121 cmask_set(cid, &qa.idle_cids.mask);
1122 else
1123 cmask_clear(cid, &qa.idle_cids.mask);
1124 }
1125
BPF_STRUCT_OPS(qmap_set_cmask,struct task_struct * p,const struct scx_cmask * cmask_in)1126 void BPF_STRUCT_OPS(qmap_set_cmask, struct task_struct *p,
1127 const struct scx_cmask *cmask_in)
1128 {
1129 struct scx_cmask __arena *cmask = (struct scx_cmask __arena *)(long)cmask_in;
1130 task_ctx_t *taskc;
1131
1132 taskc = lookup_task_ctx(p);
1133 if (!taskc)
1134 return;
1135 cmask_copy(&taskc->cpus_allowed, cmask);
1136 }
1137
1138 struct monitor_timer {
1139 struct bpf_timer timer;
1140 };
1141
1142 struct {
1143 __uint(type, BPF_MAP_TYPE_ARRAY);
1144 __uint(max_entries, 1);
1145 __type(key, u32);
1146 __type(value, struct monitor_timer);
1147 } monitor_timer SEC(".maps");
1148
1149 /*
1150 * Aggregate cidperf across the first nr_online_cids cids. Post-hotplug
1151 * the first-N-are-online invariant drifts, so some cap/cur values may
1152 * be stale. For this demo monitor that's fine; the scheduler exits on
1153 * the enable-time hotplug_seq mismatch and userspace restarts, which
1154 * rebuilds the layout.
1155 */
monitor_cpuperf(void)1156 static void monitor_cpuperf(void)
1157 {
1158 u32 nr_online = scx_bpf_nr_online_cids();
1159 u64 cap_sum = 0, cur_sum = 0, cur_min = SCX_CPUPERF_ONE, cur_max = 0;
1160 u64 target_sum = 0, target_min = SCX_CPUPERF_ONE, target_max = 0;
1161 s32 cid;
1162
1163 QMAP_TOUCH_ARENA();
1164
1165 bpf_for(cid, 0, nr_online) {
1166 struct cpu_ctx __arena *cpuc = &qa.cpu_ctxs[cid];
1167 u32 cap = scx_bpf_cidperf_cap(cid);
1168 u32 cur = scx_bpf_cidperf_cur(cid);
1169 u32 target;
1170
1171 cur_min = cur < cur_min ? cur : cur_min;
1172 cur_max = cur > cur_max ? cur : cur_max;
1173
1174 cur_sum += (u64)cur * cap / SCX_CPUPERF_ONE;
1175 cap_sum += cap;
1176
1177 target = cpuc->cpuperf_target;
1178 target_sum += target;
1179 target_min = target < target_min ? target : target_min;
1180 target_max = target > target_max ? target : target_max;
1181 }
1182
1183 if (!nr_online || !cap_sum)
1184 return;
1185
1186 qa.cpuperf_min = cur_min;
1187 qa.cpuperf_avg = cur_sum * SCX_CPUPERF_ONE / cap_sum;
1188 qa.cpuperf_max = cur_max;
1189
1190 qa.cpuperf_target_min = target_min;
1191 qa.cpuperf_target_avg = target_sum / nr_online;
1192 qa.cpuperf_target_max = target_max;
1193 }
1194
1195 /*
1196 * Dump the currently queued tasks in the shared DSQ to demonstrate the usage of
1197 * scx_bpf_dsq_nr_queued() and DSQ iterator. Raise the dispatch batch count to
1198 * see meaningful dumps in the trace pipe.
1199 */
dump_shared_dsq(void)1200 static void dump_shared_dsq(void)
1201 {
1202 struct task_struct *p;
1203 s32 nr;
1204
1205 if (!(nr = scx_bpf_dsq_nr_queued(SHARED_DSQ)))
1206 return;
1207
1208 bpf_printk("Dumping %d tasks in SHARED_DSQ in reverse order", nr);
1209
1210 bpf_rcu_read_lock();
1211 bpf_for_each(scx_dsq, p, SHARED_DSQ, SCX_DSQ_ITER_REV)
1212 bpf_printk("%s[%d]", p->comm, p->pid);
1213 bpf_rcu_read_unlock();
1214 }
1215
monitor_timerfn(void * map,int * key,struct bpf_timer * timer)1216 static int monitor_timerfn(void *map, int *key, struct bpf_timer *timer)
1217 {
1218 bpf_rcu_read_lock();
1219 scan_shared_dsq(true);
1220 bpf_rcu_read_unlock();
1221
1222 monitor_cpuperf();
1223
1224 if (print_dsqs_and_events) {
1225 struct scx_event_stats events;
1226
1227 dump_shared_dsq();
1228
1229 __COMPAT_scx_bpf_events(&events, sizeof(events));
1230
1231 bpf_printk("%35s: %lld", "SCX_EV_SELECT_CPU_FALLBACK",
1232 scx_read_event(&events, SCX_EV_SELECT_CPU_FALLBACK));
1233 bpf_printk("%35s: %lld", "SCX_EV_DISPATCH_LOCAL_DSQ_OFFLINE",
1234 scx_read_event(&events, SCX_EV_DISPATCH_LOCAL_DSQ_OFFLINE));
1235 bpf_printk("%35s: %lld", "SCX_EV_DISPATCH_KEEP_LAST",
1236 scx_read_event(&events, SCX_EV_DISPATCH_KEEP_LAST));
1237 bpf_printk("%35s: %lld", "SCX_EV_ENQ_SKIP_EXITING",
1238 scx_read_event(&events, SCX_EV_ENQ_SKIP_EXITING));
1239 bpf_printk("%35s: %lld", "SCX_EV_REFILL_SLICE_DFL",
1240 scx_read_event(&events, SCX_EV_REFILL_SLICE_DFL));
1241 bpf_printk("%35s: %lld", "SCX_EV_BYPASS_DURATION",
1242 scx_read_event(&events, SCX_EV_BYPASS_DURATION));
1243 bpf_printk("%35s: %lld", "SCX_EV_BYPASS_DISPATCH",
1244 scx_read_event(&events, SCX_EV_BYPASS_DISPATCH));
1245 bpf_printk("%35s: %lld", "SCX_EV_BYPASS_ACTIVATE",
1246 scx_read_event(&events, SCX_EV_BYPASS_ACTIVATE));
1247 }
1248
1249 bpf_timer_start(timer, ONE_SEC_IN_NS, 0);
1250 return 0;
1251 }
1252
1253 struct lowpri_timer {
1254 struct bpf_timer timer;
1255 };
1256
1257 struct {
1258 __uint(type, BPF_MAP_TYPE_ARRAY);
1259 __uint(max_entries, 1);
1260 __type(key, u32);
1261 __type(value, struct lowpri_timer);
1262 } lowpri_timer SEC(".maps");
1263
1264 /*
1265 * Nice 19 tasks are put into the lowpri DSQ. Every 10ms, reenq is triggered and
1266 * the tasks are transferred to SHARED_DSQ.
1267 */
lowpri_timerfn(void * map,int * key,struct bpf_timer * timer)1268 static int lowpri_timerfn(void *map, int *key, struct bpf_timer *timer)
1269 {
1270 scx_bpf_dsq_reenq(LOWPRI_DSQ, 0);
1271 bpf_timer_start(timer, LOWPRI_INTV_NS, 0);
1272 return 0;
1273 }
1274
1275 struct round_robin_timer {
1276 struct bpf_timer timer;
1277 };
1278
1279 struct {
1280 __uint(type, BPF_MAP_TYPE_ARRAY);
1281 __uint(max_entries, 1);
1282 __type(key, u32);
1283 __type(value, struct round_robin_timer);
1284 } round_robin_timer SEC(".maps");
1285
1286 /*
1287 * Partition update synchronization. qa.part can be written from concurrent
1288 * contexts. This single-runner guard admits one writer at a time without
1289 * holding a lock across the grant/revoke kfuncs. part_pending coalesces
1290 * repartition requests that arrive while it is held.
1291 *
1292 * They live in .bss, not the arena: rr_advance() runs from a bpf_timer
1293 * callback, where the verifier rejects atomic ops on arena memory.
1294 */
1295 static u64 part_busy;
1296 static u64 part_pending;
1297
part_try_start(void)1298 static bool part_try_start(void)
1299 {
1300 /* set busy, report whether it was previously clear (we acquired it) */
1301 return !__sync_fetch_and_or(&part_busy, 1);
1302 }
1303
part_end(void)1304 static void part_end(void)
1305 {
1306 __sync_fetch_and_and(&part_busy, 0);
1307 }
1308
1309 /*
1310 * compute_partition() scratch.
1311 *
1312 * The excl-held cids are handed out in cid order: position 0..nr_excl-1 over
1313 * the held cids is split into contiguous ranges, one per participant that gets
1314 * at least one excl cid. Range k is owned by cp_range_owner[k] and ends at the
1315 * cumulative position cp_range_end[k].
1316 */
1317 static s32 cp_range_owner[MAX_PARTS]; /* exclusive range k: its owner id ... */
1318 static s32 cp_range_end[MAX_PARTS]; /* ... and the cumulative position it ends at */
1319
1320 /* a participant in the partition: self or an attached child */
1321 struct participant {
1322 s32 slot; /* child slot, or CID_SELF */
1323 u32 weight; /* cpu.weight */
1324 };
1325
1326 /**
1327 * place_one - assign one excl-held cid to its owner
1328 * @cid: the excl-held cid to place
1329 * @n: its position among the excl-held cids, in [0, nr_excl)
1330 * @total_excl: how many positions are owned exclusively (the rest are shared)
1331 *
1332 * Position @n below @total_excl is owned exclusively. It falls in the range
1333 * whose cumulative end it is under, owned by cp_range_owner[]. A position at or
1334 * above @total_excl is the rounding leftover which joins the shared pool.
1335 *
1336 * A separate __noinline function to help verification.
1337 */
place_one(s32 cid,s32 n,s32 total_excl)1338 __noinline int place_one(s32 cid, s32 n, s32 total_excl)
1339 {
1340 s32 owner = CID_SELF, i, s;
1341
1342 if (cid < 0 || cid >= SCX_QMAP_MAX_CPUS || n < 0 || n >= SCX_QMAP_MAX_CPUS ||
1343 total_excl < 0) {
1344 scx_bpf_error("-ERANGE");
1345 return 0;
1346 }
1347
1348 if (n < total_excl) {
1349 for (i = 0; i < MAX_PARTS; i++) {
1350 if (n < cp_range_end[i]) {
1351 owner = cp_range_owner[i];
1352 break;
1353 }
1354 }
1355 qa.part.cid_owner[cid] = owner;
1356 } else {
1357 s = n - total_excl;
1358 if (s < 0 || s >= MAX_PARTS) {
1359 scx_bpf_error("-ERANGE");
1360 return 0;
1361 }
1362 qa.part.shared_cids[s] = cid;
1363 /* time-shared: dispatch resolves the live holder via rr_pos */
1364 qa.part.cid_owner[cid] = CID_SHARED;
1365 }
1366 return 0;
1367 }
1368
1369 /**
1370 * compute_partition - build the cid partition from this node's held caps
1371 *
1372 * Decide each cid's owner, the shared pool and the rr rotation. __noinline to
1373 * help verification. See the comment at the top of the file.
1374 */
compute_partition(void)1375 __noinline void compute_partition(void)
1376 {
1377 s32 nr_cids = qa.nr_cids;
1378 s32 nr_excl, total_excl = 0, nr_rr = 0;
1379 s32 sum_w, i, cid, n = 0, share, self_w;
1380 u64 cgid_snap[MAX_SUB_SCHEDS];
1381 s32 w_snap[MAX_SUB_SCHEDS];
1382
1383 if (nr_cids > SCX_QMAP_MAX_CPUS) {
1384 scx_bpf_error("-ERANGE");
1385 return;
1386 }
1387
1388 /* find out the cids we hold */
1389 scx_bpf_sub_caps(0, SCX_CAP_ENQ, &qa.held_excl.mask);
1390 scx_bpf_sub_caps(0, SCX_CAP_ENQ_IMMED, &qa.held_shared.mask);
1391 cmask_andnot(&qa.held_shared.mask, &qa.held_excl.mask); /* held only as ENQ_IMMED */
1392
1393 qa.part.nr_shared = 0;
1394 qa.part.nr_rr = 0;
1395 qa.part.rr_pos = 0;
1396
1397 nr_excl = cmask_weight(&qa.held_excl.mask);
1398 qa.part.nr_excl = nr_excl;
1399
1400 /* no excl cid: held_shared stays self-local, the rest unheld */
1401 if (!nr_excl) {
1402 bpf_for(cid, 0, nr_cids) {
1403 if (cmask_test(cid, &qa.held_shared.mask))
1404 qa.part.cid_owner[cid] = CID_SELF;
1405 else
1406 qa.part.cid_owner[cid] = CID_NONE;
1407 }
1408 return;
1409 }
1410
1411 /*
1412 * Snapshot membership and weights so the sum_w and share loops agree. A
1413 * mid-compute change would otherwise wrap nr_shared negative. The self
1414 * weight is fixed at the default: a cgroup's weight is its parent's
1415 * knob, not the scheduler's own business.
1416 */
1417 self_w = 100;
1418 bpf_for(i, 0, MAX_SUB_SCHEDS) {
1419 cgid_snap[i] = qa.sub_sched_ctxs[i].cgroup_id;
1420 w_snap[i] = cgid_snap[i] ? (qa.sub_sched_ctxs[i].weight ?: 100) : 0;
1421 }
1422
1423 /*
1424 * Participants are self plus each child. Give each a fixed range/rr
1425 * slot: self at slot 0, child i at slot i+1.
1426 *
1427 * sum_w totals every participant's weight.
1428 */
1429 sum_w = self_w;
1430 bpf_for(i, 0, MAX_SUB_SCHEDS) {
1431 barrier_var(sum_w);
1432 sum_w += w_snap[i];
1433 }
1434
1435 /*
1436 * Split [0, nr_excl) into one contiguous range per participant, each
1437 * the floor of its weight share. cp_range_owner[]/cp_range_end[] record
1438 * each range's owner and cumulative end, total_excl counts the
1439 * exclusive slots, and the rest (nr_excl - total_excl) are shared.
1440 * rr_slots[] lists every participant for the round-robin.
1441 */
1442 share = (u64)nr_excl * self_w / sum_w;
1443 total_excl += share;
1444 cp_range_owner[0] = CID_SELF;
1445 cp_range_end[0] = total_excl;
1446 qa.part.rr_slots[nr_rr++] = 0; /* self holds slot 0 (cgid 0 = no grant) */
1447
1448 bpf_for(i, 0, MAX_SUB_SCHEDS) {
1449 u64 cgid = cgid_snap[i];
1450 s32 w = w_snap[i];
1451
1452 barrier_var(total_excl);
1453 share = (u64)nr_excl * w / sum_w;
1454 total_excl += share;
1455 cp_range_owner[i + 1] = cgid ? i : CID_NONE;
1456 cp_range_end[i + 1] = total_excl;
1457
1458 if (cgid) {
1459 barrier_var(nr_rr);
1460 if (nr_rr < 0 || nr_rr >= MAX_PARTS) {
1461 scx_bpf_error("-ERANGE");
1462 return;
1463 }
1464 qa.part.rr_slots[nr_rr++] = cgid;
1465 }
1466 }
1467
1468 /* assign each cid: held-excl by position, the rest self/none */
1469 bpf_for(cid, 0, nr_cids) {
1470 if (cmask_test(cid, &qa.held_excl.mask)) {
1471 place_one(cid, n, total_excl);
1472 n++;
1473 barrier_var(n);
1474 } else if (cmask_test(cid, &qa.held_shared.mask)) {
1475 qa.part.cid_owner[cid] = CID_SELF; /* time-share, self-local */
1476 } else {
1477 qa.part.cid_owner[cid] = CID_NONE; /* not held */
1478 }
1479 }
1480
1481 qa.part.nr_shared = nr_excl - total_excl;
1482 qa.part.nr_rr = nr_rr;
1483 }
1484
1485 /*
1486 * Charge elapsed wall time to each cid's current owner. Runs under the
1487 * partition guard before every ownership change and from the stats flush, so
1488 * alloc_ns[] reflects the layout that was in effect. Shared-pool time is
1489 * charged to the live round-robin holder.
1490 */
account_alloc(void)1491 static __noinline void account_alloc(void)
1492 {
1493 u64 now = bpf_ktime_get_ns();
1494 s32 rr_owner = CID_SELF;
1495 s32 nr_cids = qa.nr_cids;
1496 u64 delta;
1497 s32 cid, i;
1498
1499 if (nr_cids < 0 || nr_cids > SCX_QMAP_MAX_CPUS) {
1500 scx_bpf_error("-ERANGE");
1501 return;
1502 }
1503
1504 /* first call starts the clock */
1505 if (!qa.alloc_ts) {
1506 qa.alloc_ts = now;
1507 return;
1508 }
1509 delta = now - qa.alloc_ts;
1510 qa.alloc_ts = now;
1511 qa.alloc_window_ns += delta;
1512
1513 /* resolve the live shared-pool holder to an owner id */
1514 if (qa.part.nr_shared && qa.part.nr_rr) {
1515 u32 pos = qa.part.rr_pos;
1516 u64 cgid = pos < MAX_PARTS ? qa.part.rr_slots[pos] : 0;
1517
1518 if (cgid) {
1519 rr_owner = CID_NONE;
1520 bpf_for(i, 0, MAX_SUB_SCHEDS)
1521 if (qa.sub_sched_ctxs[i].cgroup_id == cgid)
1522 rr_owner = i;
1523 }
1524 }
1525
1526 bpf_for(cid, 0, nr_cids) {
1527 s32 owner = qa.part.cid_owner[cid];
1528
1529 if (owner == CID_SHARED)
1530 owner = rr_owner;
1531 if (owner >= 0 && owner < MAX_SUB_SCHEDS)
1532 qa.alloc_ns[owner] += delta;
1533 else if (owner == CID_SELF)
1534 qa.self_alloc_ns += delta;
1535 }
1536 }
1537
1538 /*
1539 * apply_partition - execute the plan compute_partition() built
1540 *
1541 * Turn the owner map into the per-child, shared and self cmasks and issue the
1542 * grant/revoke kfuncs as a delta against each child's previous grant. If no
1543 * excl cid, evict every child.
1544 */
apply_partition(void)1545 __noinline void apply_partition(void)
1546 {
1547 s32 nr_cids = qa.nr_cids;
1548 s32 nr_shared = qa.part.nr_shared;
1549 s32 i, cid;
1550
1551 if (nr_cids < 0 || nr_cids > SCX_QMAP_MAX_CPUS ||
1552 nr_shared < 0 || nr_shared > MAX_PARTS) {
1553 scx_bpf_error("-ERANGE");
1554 return;
1555 }
1556
1557 /* no excl cpu: run own tasks on the held shares, evict children */
1558 if (!qa.part.nr_excl) {
1559 cmask_copy(&qa.self_cids.mask, &qa.held_shared.mask);
1560 bpf_for(i, 0, MAX_SUB_SCHEDS)
1561 if (qa.sub_sched_ctxs[i].cgroup_id)
1562 scx_bpf_sub_kill(qa.sub_sched_ctxs[i].cgroup_id,
1563 "parent holds no excl cpu to distribute");
1564 return;
1565 }
1566
1567 /*
1568 * Snapshot the old pool. The per-child revoke below clears ENQ_IMMED on
1569 * the previously-granted pool, so a cid that left the pool (now a
1570 * sibling's excl) doesn't keep a stale ENQ_IMMED on its last holder.
1571 */
1572 cmask_copy(&qa.prev_rr_cids.mask, &qa.rr_cids.mask);
1573
1574 /* turn the owner map into the rr pool, per-child excl, and self sets */
1575 cmask_init(&qa.rr_cids.mask, 0, nr_cids);
1576 cmask_init(&qa.self_cids.mask, 0, nr_cids);
1577
1578 /* snapshot each child's grant, then rebuild the new sets below */
1579 bpf_for(i, 0, MAX_SUB_SCHEDS) {
1580 cmask_copy(&qa.sub_sched_ctxs[i].prev_granted.mask,
1581 &qa.sub_sched_ctxs[i].granted_cids.mask);
1582 cmask_init(&qa.sub_sched_ctxs[i].granted_cids.mask, 0, nr_cids);
1583 }
1584
1585 bpf_for(i, 0, nr_shared)
1586 cmask_set(qa.part.shared_cids[i], &qa.rr_cids.mask);
1587 bpf_for(cid, 0, nr_cids) {
1588 s32 o = qa.part.cid_owner[cid];
1589
1590 if (cmask_test(cid, &qa.rr_cids.mask))
1591 continue;
1592 if (o >= 0 && o < MAX_SUB_SCHEDS)
1593 cmask_set(cid, &qa.sub_sched_ctxs[o].granted_cids.mask);
1594 else if (o == CID_SELF)
1595 cmask_set(cid, &qa.self_cids.mask);
1596 }
1597
1598 /*
1599 * Apply each child's exclusive cids as a delta against its previous
1600 * grant. Separately clear the previous shared grant (ENQ_IMMED on the
1601 * old pool), covering cids still pooled and cids that left for a
1602 * sibling's excl. The current holder is granted the new pool below.
1603 */
1604 bpf_for(i, 0, MAX_SUB_SCHEDS) {
1605 struct sub_sched_ctx __arena *ssc = &qa.sub_sched_ctxs[i];
1606 u64 cgid = ssc->cgroup_id;
1607
1608 if (!cgid)
1609 continue;
1610
1611 cmask_copy(&qa.to_revoke_cids.mask, &ssc->prev_granted.mask);
1612 cmask_andnot(&qa.to_revoke_cids.mask, &ssc->granted_cids.mask);
1613 cmask_copy(&qa.to_grant_cids.mask, &ssc->granted_cids.mask);
1614 cmask_andnot(&qa.to_grant_cids.mask, &ssc->prev_granted.mask);
1615
1616 scx_bpf_sub_revoke(cgid, SCX_CAP_ENQ_IMMED | SCX_CAP_PERF,
1617 &qa.prev_rr_cids.mask);
1618 scx_bpf_sub_revoke(cgid, SCX_CAP_ENQ | SCX_CAP_PREEMPT |
1619 SCX_CAP_ENQ_IMMED | SCX_CAP_PERF,
1620 &qa.to_revoke_cids.mask);
1621 scx_bpf_sub_grant(cgid, SCX_CAP_ENQ | SCX_CAP_PREEMPT |
1622 SCX_CAP_ENQ_IMMED | SCX_CAP_PERF,
1623 &qa.to_grant_cids.mask, NULL);
1624 }
1625
1626 /* the current holder of the shared pool gets ENQ_IMMED on all of it */
1627 if (nr_shared) {
1628 s32 pos = qa.part.rr_pos;
1629 u64 holder_cgid;
1630
1631 if (pos < 0 || pos >= MAX_PARTS) {
1632 scx_bpf_error("-ERANGE");
1633 return;
1634 }
1635
1636 holder_cgid = qa.part.rr_slots[pos]; /* 0 = self, nothing to grant */
1637 if (holder_cgid)
1638 scx_bpf_sub_grant(holder_cgid,
1639 SCX_CAP_ENQ_IMMED | SCX_CAP_PERF,
1640 &qa.rr_cids.mask, NULL);
1641 }
1642 }
1643
1644 /*
1645 * Recompute the split off the node's held caps and apply it. The contexts this
1646 * runs from (the sub-sched and cgroup callbacks, the rr timer) are not
1647 * serialized by the kernel, so a single runner does the work. A caller that
1648 * finds the guard held leaves part_pending set; the holder drains it before
1649 * releasing, with the rr timer as a backstop.
1650 */
redistribute(void)1651 static void redistribute(void)
1652 {
1653 s32 i;
1654
1655 __sync_fetch_and_or(&part_pending, 1);
1656
1657 if (!part_try_start())
1658 return;
1659
1660 bpf_for(i, 0, 1024) {
1661 __sync_fetch_and_and(&part_pending, 0);
1662 /* charge elapsed time to the current partition before rebuilding it */
1663 account_alloc();
1664 compute_partition();
1665 apply_partition();
1666 if (!__sync_fetch_and_or(&part_pending, 0))
1667 break;
1668 }
1669
1670 part_end();
1671 }
1672
1673 /*
1674 * Userspace pokes this (PROG_RUN) to bring alloc_ns[] current before reading
1675 * it for the stats display. Skipping when the partition guard is held is
1676 * fine - alloc_ts is untouched, so the elapsed time is charged next time.
1677 */
1678 SEC("syscall")
flush_alloc(void * ctx)1679 int flush_alloc(void *ctx)
1680 {
1681 if (part_try_start()) {
1682 account_alloc();
1683 part_end();
1684 }
1685 return 0;
1686 }
1687
1688 /*
1689 * Hand the shared pool to the next participant in the rotation. Self's turn
1690 * just revokes the pool back to this sched. A child's turn grants it ENQ_IMMED
1691 * on the entire pool. As only excl-held cids are time-shared, a wall-clock
1692 * rotation works. Driven by the round-robin timer.
1693 */
rr_advance(void)1694 static void rr_advance(void)
1695 {
1696 s32 nr_shared, old_pos, new_pos;
1697 u64 old_cgid, new_cgid;
1698 u32 nr_rr; /* unsigned for % */
1699
1700 /* a redistribute holds the partition and rebuilds the pool, so skip */
1701 if (!part_try_start())
1702 return;
1703
1704 nr_rr = qa.part.nr_rr;
1705 nr_shared = qa.part.nr_shared;
1706
1707 if (nr_shared < 0 || nr_shared > MAX_PARTS) {
1708 scx_bpf_error("-ERANGE");
1709 return;
1710 }
1711
1712 if (nr_shared && nr_rr >= 2) {
1713 /* close out the outgoing holder's pool time */
1714 account_alloc();
1715
1716 old_pos = qa.part.rr_pos;
1717 new_pos = (old_pos + 1) % nr_rr;
1718 old_cgid = qa.part.rr_slots[old_pos];
1719 new_cgid = qa.part.rr_slots[new_pos];
1720 qa.part.rr_pos = new_pos;
1721
1722 /*
1723 * Move the ENQ_IMMED cap to the next participant. The shared
1724 * cids stay marked CID_SHARED. qmap_dispatch() resolves the
1725 * live holder via rr_pos without the guard, so a dispatch
1726 * racing this handoff may reenqueue a task once. Harmless for a
1727 * time-share.
1728 */
1729 if (old_cgid)
1730 scx_bpf_sub_revoke(old_cgid,
1731 SCX_CAP_ENQ_IMMED | SCX_CAP_PERF,
1732 &qa.rr_cids.mask);
1733 if (new_cgid)
1734 scx_bpf_sub_grant(new_cgid,
1735 SCX_CAP_ENQ_IMMED | SCX_CAP_PERF,
1736 &qa.rr_cids.mask, NULL);
1737 }
1738
1739 part_end();
1740
1741 /* a resplit queued while we held the guard supersedes this rotation */
1742 if (__sync_fetch_and_or(&part_pending, 0))
1743 redistribute();
1744 }
1745
1746 /* advance the time-shared cid pool every round_robin_ns */
round_robin_timerfn(void * map,int * key,struct bpf_timer * timer)1747 static int round_robin_timerfn(void *map, int *key, struct bpf_timer *timer)
1748 {
1749 rr_advance();
1750 bpf_timer_start(timer, round_robin_ns, 0);
1751 return 0;
1752 }
1753
1754 /*
1755 * Custom cid layout for the cid-override test. On invalid input the kfunc
1756 * scx_error()s and aborts the scheduler.
1757 */
BPF_STRUCT_OPS_SLEEPABLE(qmap_init_cids)1758 s32 BPF_STRUCT_OPS_SLEEPABLE(qmap_init_cids)
1759 {
1760 u32 nr_cpu_ids = scx_bpf_nr_cpu_ids();
1761
1762 if (!cid_override_mode)
1763 return 0;
1764
1765 /* the arena arrays are sized SCX_QMAP_MAX_CPUS */
1766 if (nr_cpu_ids > SCX_QMAP_MAX_CPUS) {
1767 scx_bpf_error("nr_cpu_ids=%u exceeds SCX_QMAP_MAX_CPUS=%d",
1768 nr_cpu_ids, SCX_QMAP_MAX_CPUS);
1769 return -EINVAL;
1770 }
1771
1772 scx_bpf_cid_override(qa.cid_override_cpu_to_cid, nr_cpu_ids,
1773 qa.cid_override_shard_start, cid_override_nr_shards);
1774 return 0;
1775 }
1776
BPF_STRUCT_OPS_SLEEPABLE(qmap_init)1777 s32 BPF_STRUCT_OPS_SLEEPABLE(qmap_init)
1778 {
1779 u8 __arena *slab;
1780 u32 nr_pages, key = 0, i;
1781 u32 nr_cids, nr_cpu_ids;
1782 struct bpf_timer *timer;
1783 s32 ret;
1784
1785 nr_cids = scx_bpf_nr_cids();
1786 nr_cpu_ids = scx_bpf_nr_cpu_ids();
1787
1788 if (nr_cids > SCX_QMAP_MAX_CPUS) {
1789 scx_bpf_error("nr_cids=%u exceeds SCX_QMAP_MAX_CPUS=%d",
1790 nr_cids, SCX_QMAP_MAX_CPUS);
1791 return -EINVAL;
1792 }
1793 if (nr_cpu_ids > SCX_QMAP_MAX_CPUS) {
1794 scx_bpf_error("nr_cpu_ids=%u exceeds SCX_QMAP_MAX_CPUS=%d",
1795 nr_cpu_ids, SCX_QMAP_MAX_CPUS);
1796 return -EINVAL;
1797 }
1798
1799 /*
1800 * Allocate the task_ctx slab in arena and thread the entire slab onto
1801 * the free list. max_tasks is set by userspace before load. Each entry
1802 * is TASK_CTX_STRIDE bytes - task_ctx's trailing cpus_allowed flex
1803 * array extends into the stride tail.
1804 */
1805 if (!max_tasks) {
1806 scx_bpf_error("max_tasks must be > 0");
1807 return -EINVAL;
1808 }
1809
1810 nr_pages = (max_tasks * TASK_CTX_STRIDE + PAGE_SIZE - 1) / PAGE_SIZE;
1811 slab = bpf_arena_alloc_pages(&arena, NULL, nr_pages, NUMA_NO_NODE, 0);
1812 if (!slab) {
1813 scx_bpf_error("failed to allocate task_ctx slab");
1814 return -ENOMEM;
1815 }
1816 qa.task_ctxs = (task_ctx_t *)slab;
1817
1818 bpf_for(i, 0, 5)
1819 qa.fifos[i].idx = i;
1820
1821 bpf_for(i, 0, max_tasks) {
1822 task_ctx_t *cur = (task_ctx_t *)(slab + i * TASK_CTX_STRIDE);
1823 task_ctx_t *next = (i + 1 < max_tasks) ?
1824 (task_ctx_t *)(slab + (i + 1) * TASK_CTX_STRIDE) : NULL;
1825 cur->next_free = next;
1826 }
1827 qa.task_free_head = (task_ctx_t *)slab;
1828
1829 /* cache the cid count, trusted to be <= SCX_QMAP_MAX_CPUS hereafter */
1830 qa.nr_cids = nr_cids;
1831
1832 /* cmasks are embedded in qa, so they only need initializing */
1833 cmask_init(&qa.idle_cids.mask, 0, nr_cids);
1834 cmask_init(&qa.rr_cids.mask, 0, nr_cids);
1835 cmask_init(&qa.prev_rr_cids.mask, 0, nr_cids);
1836 cmask_init(&qa.self_cids.mask, 0, nr_cids);
1837 cmask_init(&qa.to_revoke_cids.mask, 0, nr_cids);
1838 cmask_init(&qa.to_grant_cids.mask, 0, nr_cids);
1839 cmask_init(&qa.held_excl.mask, 0, nr_cids);
1840 cmask_init(&qa.held_shared.mask, 0, nr_cids);
1841
1842 scx_bpf_sub_caps(0, SCX_CAP_ENQ, &qa.held_excl.mask);
1843 scx_bpf_sub_caps(0, SCX_CAP_ENQ_IMMED, &qa.held_shared.mask);
1844 cmask_andnot(&qa.held_shared.mask, &qa.held_excl.mask);
1845
1846 bpf_for(i, 0, MAX_SUB_SCHEDS) {
1847 cmask_init(&qa.sub_sched_ctxs[i].granted_cids.mask, 0, nr_cids);
1848 cmask_init(&qa.sub_sched_ctxs[i].prev_granted.mask, 0, nr_cids);
1849 }
1850
1851 /*
1852 * The root starts holding every cid. qmap_sub_ecaps_updated() maintains
1853 * per-cid shared state as effective caps settle, and redistribute()
1854 * rebuilds owner and self from held caps. A non-root node starts with
1855 * nothing.
1856 */
1857 bpf_for(i, 0, nr_cids) {
1858 if (!sub_cgroup_id) {
1859 cmask_set(i, &qa.self_cids.mask);
1860 qa.part.cid_owner[i] = CID_SELF;
1861 } else {
1862 qa.part.cid_owner[i] = CID_NONE;
1863 }
1864 }
1865 qa.part.nr_shared = 0;
1866
1867 ret = scx_bpf_create_dsq(SHARED_DSQ, -1);
1868 if (ret) {
1869 scx_bpf_error("failed to create DSQ %d (%d)", SHARED_DSQ, ret);
1870 return ret;
1871 }
1872
1873 ret = scx_bpf_create_dsq(HIGHPRI_DSQ, -1);
1874 if (ret) {
1875 scx_bpf_error("failed to create DSQ %d (%d)", HIGHPRI_DSQ, ret);
1876 return ret;
1877 }
1878
1879 ret = scx_bpf_create_dsq(LOWPRI_DSQ, -1);
1880 if (ret)
1881 return ret;
1882
1883 timer = bpf_map_lookup_elem(&monitor_timer, &key);
1884 if (!timer)
1885 return -ESRCH;
1886 bpf_timer_init(timer, &monitor_timer, CLOCK_MONOTONIC);
1887 bpf_timer_set_callback(timer, monitor_timerfn);
1888 ret = bpf_timer_start(timer, ONE_SEC_IN_NS, 0);
1889 if (ret)
1890 return ret;
1891
1892 if (__COMPAT_has_generic_reenq()) {
1893 /* see lowpri_timerfn() */
1894 timer = bpf_map_lookup_elem(&lowpri_timer, &key);
1895 if (!timer)
1896 return -ESRCH;
1897 bpf_timer_init(timer, &lowpri_timer, CLOCK_MONOTONIC);
1898 bpf_timer_set_callback(timer, lowpri_timerfn);
1899 ret = bpf_timer_start(timer, LOWPRI_INTV_NS, 0);
1900 if (ret)
1901 return ret;
1902 }
1903
1904 /* sub-sched: drive the boundary-cid round-robin from a bpf timer */
1905 timer = bpf_map_lookup_elem(&round_robin_timer, &key);
1906 if (!timer)
1907 return -ESRCH;
1908 bpf_timer_init(timer, &round_robin_timer, CLOCK_MONOTONIC);
1909 bpf_timer_set_callback(timer, round_robin_timerfn);
1910 ret = bpf_timer_start(timer, round_robin_ns, 0);
1911 if (ret)
1912 return ret;
1913
1914 return 0;
1915 }
1916
BPF_STRUCT_OPS(qmap_exit,struct scx_exit_info * ei)1917 void BPF_STRUCT_OPS(qmap_exit, struct scx_exit_info *ei)
1918 {
1919 UEI_RECORD(uei, ei);
1920 }
1921
1922 /*
1923 * Seed a new sub slot with the cgroup's current weight. The kernel delivers
1924 * ops.cpuctl_set_weight() only on value-changing writes, so a weight set
1925 * before the sub attached would otherwise go unnoticed.
1926 */
cgrp_cur_weight(u64 cgid)1927 static u32 cgrp_cur_weight(u64 cgid)
1928 {
1929 struct cgroup_subsys_state *css;
1930 struct cgroup *cgrp;
1931 u32 weight = 100;
1932
1933 cgrp = bpf_cgroup_from_id(cgid);
1934 if (!cgrp)
1935 return weight;
1936
1937 css = BPF_CORE_READ(cgrp, subsys[cpu_cgrp_id]);
1938 if (css) {
1939 struct task_group *tg = container_of(css, struct task_group, css);
1940 u32 w = BPF_CORE_READ(tg, scx.weight);
1941
1942 if (w)
1943 weight = w;
1944 }
1945 bpf_cgroup_release(cgrp);
1946 return weight;
1947 }
1948
BPF_STRUCT_OPS(qmap_sub_attach,struct scx_sub_attach_args * args)1949 s32 BPF_STRUCT_OPS(qmap_sub_attach, struct scx_sub_attach_args *args)
1950 {
1951 s32 i;
1952
1953 /* as long as there is at least one excl cpu, children can attach */
1954 if (!cmask_weight(&qa.held_excl.mask))
1955 return -ENOSPC;
1956
1957 for (i = 0; i < MAX_SUB_SCHEDS; i++) {
1958 if (qa.sub_sched_ctxs[i].cgroup_id)
1959 continue;
1960
1961 qa.sub_sched_ctxs[i].cgroup_id = args->ops->sub_cgroup_id;
1962 qa.sub_sched_ctxs[i].weight = cgrp_cur_weight(args->ops->sub_cgroup_id);
1963 qa.nr_sub_scheds++;
1964 bpf_printk("attaching sub-sched[%d] on %s", i, args->cgroup_path);
1965 redistribute();
1966 return 0;
1967 }
1968
1969 return -ENOSPC;
1970 }
1971
BPF_STRUCT_OPS(qmap_sub_detach,struct scx_sub_detach_args * args)1972 void BPF_STRUCT_OPS(qmap_sub_detach, struct scx_sub_detach_args *args)
1973 {
1974 s32 i;
1975
1976 for (i = 0; i < MAX_SUB_SCHEDS; i++) {
1977 if (qa.sub_sched_ctxs[i].cgroup_id != args->ops->sub_cgroup_id)
1978 continue;
1979
1980 qa.sub_sched_ctxs[i].cgroup_id = 0;
1981 qa.sub_sched_ctxs[i].weight = 100;
1982 cmask_init(&qa.sub_sched_ctxs[i].granted_cids.mask, 0, qa.nr_cids);
1983 qa.nr_sub_scheds--;
1984 bpf_printk("detaching sub-sched[%d] on %s", i, args->cgroup_path);
1985 redistribute();
1986 break;
1987 }
1988 }
1989
BPF_STRUCT_OPS(qmap_sub_caps_updated,const struct scx_cmask * cmask,u64 caps)1990 void BPF_STRUCT_OPS(qmap_sub_caps_updated, const struct scx_cmask *cmask, u64 caps)
1991 {
1992 /* our held caps changed, redistribute */
1993 redistribute();
1994 }
1995
BPF_STRUCT_OPS(qmap_sub_ecaps_updated,s32 cid,u64 before,u64 after)1996 void BPF_STRUCT_OPS(qmap_sub_ecaps_updated, s32 cid, u64 before, u64 after)
1997 {
1998 /*
1999 * Effective caps updated. Track which cids hold shared caps so a self
2000 * task placed there enqueues IMMED.
2001 */
2002 if (after & SCX_CAP_ENQ_IMMED)
2003 qa.cid_shared[cid] = (after & SCX_CAP_ENQ) ? 0 : 1;
2004 else
2005 qa.cid_shared[cid] = 0;
2006 }
2007
2008 SCX_OPS_CID_DEFINE(qmap_ops,
2009 .flags = SCX_OPS_ENQ_EXITING | SCX_OPS_TID_TO_TASK,
2010 .select_cid = (void *)qmap_select_cid,
2011 .enqueue = (void *)qmap_enqueue,
2012 .dequeue = (void *)qmap_dequeue,
2013 .dispatch = (void *)qmap_dispatch,
2014 .tick = (void *)qmap_tick,
2015 .core_sched_before = (void *)qmap_core_sched_before,
2016 .set_cmask = (void *)qmap_set_cmask,
2017 .update_idle = (void *)qmap_update_idle,
2018 .init_task = (void *)qmap_init_task,
2019 .exit_task = (void *)qmap_exit_task,
2020 .dump = (void *)qmap_dump,
2021 .dump_cid = (void *)qmap_dump_cid,
2022 .dump_task = (void *)qmap_dump_task,
2023 .cpuctl_init = (void *)qmap_cpuctl_init,
2024 .cpuctl_set_weight = (void *)qmap_cpuctl_set_weight,
2025 .cpuctl_set_bandwidth = (void *)qmap_cpuctl_set_bandwidth,
2026 .cpuctl_move = (void *)qmap_cpuctl_move,
2027 .sub_attach = (void *)qmap_sub_attach,
2028 .sub_detach = (void *)qmap_sub_detach,
2029 .sub_caps_updated = (void *)qmap_sub_caps_updated,
2030 .sub_ecaps_updated = (void *)qmap_sub_ecaps_updated,
2031 .init_cids = (void *)qmap_init_cids,
2032 .init = (void *)qmap_init,
2033 .exit = (void *)qmap_exit,
2034 .timeout_ms = 5000U,
2035 .name = "qmap");
2036