xref: /linux/tools/sched_ext/scx_qmap.bpf.c (revision 11260c335ec6071af5543aef73000b28f041c124)
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