xref: /linux/tools/sched_ext/scx_qmap.bpf.c (revision bf1079577a116f0685e7025b9ee2547345ee1c63)
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 	if (bpf_timer_start(timer, ONE_SEC_IN_NS, 0))
1250 		scx_bpf_error("failed to re-arm stats timer");
1251 	return 0;
1252 }
1253 
1254 struct lowpri_timer {
1255 	struct bpf_timer timer;
1256 };
1257 
1258 struct {
1259 	__uint(type, BPF_MAP_TYPE_ARRAY);
1260 	__uint(max_entries, 1);
1261 	__type(key, u32);
1262 	__type(value, struct lowpri_timer);
1263 } lowpri_timer SEC(".maps");
1264 
1265 /*
1266  * Nice 19 tasks are put into the lowpri DSQ. Every 10ms, reenq is triggered and
1267  * the tasks are transferred to SHARED_DSQ.
1268  */
lowpri_timerfn(void * map,int * key,struct bpf_timer * timer)1269 static int lowpri_timerfn(void *map, int *key, struct bpf_timer *timer)
1270 {
1271 	scx_bpf_dsq_reenq(LOWPRI_DSQ, 0);
1272 	if (bpf_timer_start(timer, LOWPRI_INTV_NS, 0))
1273 		scx_bpf_error("failed to re-arm lowpri timer");
1274 	return 0;
1275 }
1276 
1277 struct round_robin_timer {
1278 	struct bpf_timer timer;
1279 };
1280 
1281 struct {
1282 	__uint(type, BPF_MAP_TYPE_ARRAY);
1283 	__uint(max_entries, 1);
1284 	__type(key, u32);
1285 	__type(value, struct round_robin_timer);
1286 } round_robin_timer SEC(".maps");
1287 
1288 /*
1289  * Partition update synchronization. qa.part can be written from concurrent
1290  * contexts. This single-runner guard admits one writer at a time without
1291  * holding a lock across the grant/revoke kfuncs. part_pending coalesces
1292  * repartition requests that arrive while it is held.
1293  *
1294  * They live in .bss, not the arena: rr_advance() runs from a bpf_timer
1295  * callback, where the verifier rejects atomic ops on arena memory.
1296  */
1297 static u64 part_busy;
1298 static u64 part_pending;
1299 
part_try_start(void)1300 static bool part_try_start(void)
1301 {
1302 	/* set busy, report whether it was previously clear (we acquired it) */
1303 	return !__sync_fetch_and_or(&part_busy, 1);
1304 }
1305 
part_end(void)1306 static void part_end(void)
1307 {
1308 	__sync_fetch_and_and(&part_busy, 0);
1309 }
1310 
1311 /*
1312  * compute_partition() scratch.
1313  *
1314  * The excl-held cids are handed out in cid order: position 0..nr_excl-1 over
1315  * the held cids is split into contiguous ranges, one per participant that gets
1316  * at least one excl cid. Range k is owned by cp_range_owner[k] and ends at the
1317  * cumulative position cp_range_end[k].
1318  */
1319 static s32 cp_range_owner[MAX_PARTS];	/* exclusive range k: its owner id ... */
1320 static s32 cp_range_end[MAX_PARTS];	/* ... and the cumulative position it ends at */
1321 
1322 /* a participant in the partition: self or an attached child */
1323 struct participant {
1324 	s32 slot;	/* child slot, or CID_SELF */
1325 	u32 weight;	/* cpu.weight */
1326 };
1327 
1328 /**
1329  * place_one - assign one excl-held cid to its owner
1330  * @cid: the excl-held cid to place
1331  * @n: its position among the excl-held cids, in [0, nr_excl)
1332  * @total_excl:	how many positions are owned exclusively (the rest are shared)
1333  *
1334  * Position @n below @total_excl is owned exclusively. It falls in the range
1335  * whose cumulative end it is under, owned by cp_range_owner[]. A position at or
1336  * above @total_excl is the rounding leftover which joins the shared pool.
1337  *
1338  * A separate __noinline function to help verification.
1339  */
place_one(s32 cid,s32 n,s32 total_excl)1340 __noinline int place_one(s32 cid, s32 n, s32 total_excl)
1341 {
1342 	s32 owner = CID_SELF, i, s;
1343 
1344 	if (cid < 0 || cid >= SCX_QMAP_MAX_CPUS || n < 0 || n >= SCX_QMAP_MAX_CPUS ||
1345 	    total_excl < 0) {
1346 		scx_bpf_error("-ERANGE");
1347 		return 0;
1348 	}
1349 
1350 	if (n < total_excl) {
1351 		for (i = 0; i < MAX_PARTS; i++) {
1352 			if (n < cp_range_end[i]) {
1353 				owner = cp_range_owner[i];
1354 				break;
1355 			}
1356 		}
1357 		qa.part.cid_owner[cid] = owner;
1358 	} else {
1359 		s = n - total_excl;
1360 		if (s < 0 || s >= MAX_PARTS) {
1361 			scx_bpf_error("-ERANGE");
1362 			return 0;
1363 		}
1364 		qa.part.shared_cids[s] = cid;
1365 		/* time-shared: dispatch resolves the live holder via rr_pos */
1366 		qa.part.cid_owner[cid] = CID_SHARED;
1367 	}
1368 	return 0;
1369 }
1370 
1371 /**
1372  * compute_partition - build the cid partition from this node's held caps
1373  *
1374  * Decide each cid's owner, the shared pool and the rr rotation. __noinline to
1375  * help verification. See the comment at the top of the file.
1376  */
compute_partition(void)1377 __noinline void compute_partition(void)
1378 {
1379 	s32 nr_cids = qa.nr_cids;
1380 	s32 nr_excl, total_excl = 0, nr_rr = 0;
1381 	s32 sum_w, i, cid, n = 0, share, self_w;
1382 	u64 cgid_snap[MAX_SUB_SCHEDS];
1383 	s32 w_snap[MAX_SUB_SCHEDS];
1384 
1385 	if (nr_cids > SCX_QMAP_MAX_CPUS) {
1386 		scx_bpf_error("-ERANGE");
1387 		return;
1388 	}
1389 
1390 	/* find out the cids we hold */
1391 	scx_bpf_sub_caps(0, SCX_CAP_ENQ, &qa.held_excl.mask);
1392 	scx_bpf_sub_caps(0, SCX_CAP_ENQ_IMMED, &qa.held_shared.mask);
1393 	cmask_andnot(&qa.held_shared.mask, &qa.held_excl.mask);	/* held only as ENQ_IMMED */
1394 
1395 	qa.part.nr_shared = 0;
1396 	qa.part.nr_rr = 0;
1397 	qa.part.rr_pos = 0;
1398 
1399 	nr_excl = cmask_weight(&qa.held_excl.mask);
1400 	qa.part.nr_excl = nr_excl;
1401 
1402 	/* no excl cid: held_shared stays self-local, the rest unheld */
1403 	if (!nr_excl) {
1404 		bpf_for(cid, 0, nr_cids) {
1405 			if (cmask_test(cid, &qa.held_shared.mask))
1406 				qa.part.cid_owner[cid] = CID_SELF;
1407 			else
1408 				qa.part.cid_owner[cid] = CID_NONE;
1409 		}
1410 		return;
1411 	}
1412 
1413 	/*
1414 	 * Snapshot membership and weights so the sum_w and share loops agree. A
1415 	 * mid-compute change would otherwise wrap nr_shared negative. The self
1416 	 * weight is fixed at the default: a cgroup's weight is its parent's
1417 	 * knob, not the scheduler's own business.
1418 	 */
1419 	self_w = 100;
1420 	bpf_for(i, 0, MAX_SUB_SCHEDS) {
1421 		cgid_snap[i] = qa.sub_sched_ctxs[i].cgroup_id;
1422 		w_snap[i] = cgid_snap[i] ? (qa.sub_sched_ctxs[i].weight ?: 100) : 0;
1423 	}
1424 
1425 	/*
1426 	 * Participants are self plus each child. Give each a fixed range/rr
1427 	 * slot: self at slot 0, child i at slot i+1.
1428 	 *
1429 	 * sum_w totals every participant's weight.
1430 	 */
1431 	sum_w = self_w;
1432 	bpf_for(i, 0, MAX_SUB_SCHEDS) {
1433 		barrier_var(sum_w);
1434 		sum_w += w_snap[i];
1435 	}
1436 
1437 	/*
1438 	 * Split [0, nr_excl) into one contiguous range per participant, each
1439 	 * the floor of its weight share. cp_range_owner[]/cp_range_end[] record
1440 	 * each range's owner and cumulative end, total_excl counts the
1441 	 * exclusive slots, and the rest (nr_excl - total_excl) are shared.
1442 	 * rr_slots[] lists every participant for the round-robin.
1443 	 */
1444 	share = (u64)nr_excl * self_w / sum_w;
1445 	total_excl += share;
1446 	cp_range_owner[0] = CID_SELF;
1447 	cp_range_end[0] = total_excl;
1448 	qa.part.rr_slots[nr_rr++] = 0;		/* self holds slot 0 (cgid 0 = no grant) */
1449 
1450 	bpf_for(i, 0, MAX_SUB_SCHEDS) {
1451 		u64 cgid = cgid_snap[i];
1452 		s32 w = w_snap[i];
1453 
1454 		barrier_var(total_excl);
1455 		share = (u64)nr_excl * w / sum_w;
1456 		total_excl += share;
1457 		cp_range_owner[i + 1] = cgid ? i : CID_NONE;
1458 		cp_range_end[i + 1] = total_excl;
1459 
1460 		if (cgid) {
1461 			barrier_var(nr_rr);
1462 			if (nr_rr < 0 || nr_rr >= MAX_PARTS) {
1463 				scx_bpf_error("-ERANGE");
1464 				return;
1465 			}
1466 			qa.part.rr_slots[nr_rr++] = cgid;
1467 		}
1468 	}
1469 
1470 	/* assign each cid: held-excl by position, the rest self/none */
1471 	bpf_for(cid, 0, nr_cids) {
1472 		if (cmask_test(cid, &qa.held_excl.mask)) {
1473 			place_one(cid, n, total_excl);
1474 			n++;
1475 			barrier_var(n);
1476 		} else if (cmask_test(cid, &qa.held_shared.mask)) {
1477 			qa.part.cid_owner[cid] = CID_SELF;	/* time-share, self-local */
1478 		} else {
1479 			qa.part.cid_owner[cid] = CID_NONE;	/* not held */
1480 		}
1481 	}
1482 
1483 	qa.part.nr_shared = nr_excl - total_excl;
1484 	qa.part.nr_rr = nr_rr;
1485 }
1486 
1487 /*
1488  * Charge elapsed wall time to each cid's current owner. Runs under the
1489  * partition guard before every ownership change and from the stats flush, so
1490  * alloc_ns[] reflects the layout that was in effect. Shared-pool time is
1491  * charged to the live round-robin holder.
1492  */
account_alloc(void)1493 static __noinline void account_alloc(void)
1494 {
1495 	u64 now = bpf_ktime_get_ns();
1496 	s32 rr_owner = CID_SELF;
1497 	s32 nr_cids = qa.nr_cids;
1498 	u64 delta;
1499 	s32 cid, i;
1500 
1501 	if (nr_cids < 0 || nr_cids > SCX_QMAP_MAX_CPUS) {
1502 		scx_bpf_error("-ERANGE");
1503 		return;
1504 	}
1505 
1506 	/* first call starts the clock */
1507 	if (!qa.alloc_ts) {
1508 		qa.alloc_ts = now;
1509 		return;
1510 	}
1511 	delta = now - qa.alloc_ts;
1512 	qa.alloc_ts = now;
1513 	qa.alloc_window_ns += delta;
1514 
1515 	/* resolve the live shared-pool holder to an owner id */
1516 	if (qa.part.nr_shared && qa.part.nr_rr) {
1517 		u32 pos = qa.part.rr_pos;
1518 		u64 cgid = pos < MAX_PARTS ? qa.part.rr_slots[pos] : 0;
1519 
1520 		if (cgid) {
1521 			rr_owner = CID_NONE;
1522 			bpf_for(i, 0, MAX_SUB_SCHEDS)
1523 				if (qa.sub_sched_ctxs[i].cgroup_id == cgid)
1524 					rr_owner = i;
1525 		}
1526 	}
1527 
1528 	bpf_for(cid, 0, nr_cids) {
1529 		s32 owner = qa.part.cid_owner[cid];
1530 
1531 		if (owner == CID_SHARED)
1532 			owner = rr_owner;
1533 		if (owner >= 0 && owner < MAX_SUB_SCHEDS)
1534 			qa.alloc_ns[owner] += delta;
1535 		else if (owner == CID_SELF)
1536 			qa.self_alloc_ns += delta;
1537 	}
1538 }
1539 
1540 /*
1541  * apply_partition - execute the plan compute_partition() built
1542  *
1543  * Turn the owner map into the per-child, shared and self cmasks and issue the
1544  * grant/revoke kfuncs as a delta against each child's previous grant. If no
1545  * excl cid, evict every child.
1546  */
apply_partition(void)1547 __noinline void apply_partition(void)
1548 {
1549 	s32 nr_cids = qa.nr_cids;
1550 	s32 nr_shared = qa.part.nr_shared;
1551 	s32 i, cid;
1552 
1553 	if (nr_cids < 0 || nr_cids > SCX_QMAP_MAX_CPUS ||
1554 	    nr_shared < 0 || nr_shared > MAX_PARTS) {
1555 		scx_bpf_error("-ERANGE");
1556 		return;
1557 	}
1558 
1559 	/* no excl cpu: run own tasks on the held shares, evict children */
1560 	if (!qa.part.nr_excl) {
1561 		cmask_copy(&qa.self_cids.mask, &qa.held_shared.mask);
1562 		bpf_for(i, 0, MAX_SUB_SCHEDS)
1563 			if (qa.sub_sched_ctxs[i].cgroup_id)
1564 				scx_bpf_sub_kill(qa.sub_sched_ctxs[i].cgroup_id,
1565 						 "parent holds no excl cpu to distribute");
1566 		return;
1567 	}
1568 
1569 	/*
1570 	 * Snapshot the old pool. The per-child revoke below clears ENQ_IMMED on
1571 	 * the previously-granted pool, so a cid that left the pool (now a
1572 	 * sibling's excl) doesn't keep a stale ENQ_IMMED on its last holder.
1573 	 */
1574 	cmask_copy(&qa.prev_rr_cids.mask, &qa.rr_cids.mask);
1575 
1576 	/* turn the owner map into the rr pool, per-child excl, and self sets */
1577 	cmask_init(&qa.rr_cids.mask, 0, nr_cids);
1578 	cmask_init(&qa.self_cids.mask, 0, nr_cids);
1579 
1580 	/* snapshot each child's grant, then rebuild the new sets below */
1581 	bpf_for(i, 0, MAX_SUB_SCHEDS) {
1582 		cmask_copy(&qa.sub_sched_ctxs[i].prev_granted.mask,
1583 			   &qa.sub_sched_ctxs[i].granted_cids.mask);
1584 		cmask_init(&qa.sub_sched_ctxs[i].granted_cids.mask, 0, nr_cids);
1585 	}
1586 
1587 	bpf_for(i, 0, nr_shared)
1588 		cmask_set(qa.part.shared_cids[i], &qa.rr_cids.mask);
1589 	bpf_for(cid, 0, nr_cids) {
1590 		s32 o = qa.part.cid_owner[cid];
1591 
1592 		if (cmask_test(cid, &qa.rr_cids.mask))
1593 			continue;
1594 		if (o >= 0 && o < MAX_SUB_SCHEDS)
1595 			cmask_set(cid, &qa.sub_sched_ctxs[o].granted_cids.mask);
1596 		else if (o == CID_SELF)
1597 			cmask_set(cid, &qa.self_cids.mask);
1598 	}
1599 
1600 	/*
1601 	 * Apply each child's exclusive cids as a delta against its previous
1602 	 * grant. Separately clear the previous shared grant (ENQ_IMMED on the
1603 	 * old pool), covering cids still pooled and cids that left for a
1604 	 * sibling's excl. The current holder is granted the new pool below.
1605 	 */
1606 	bpf_for(i, 0, MAX_SUB_SCHEDS) {
1607 		struct sub_sched_ctx __arena *ssc = &qa.sub_sched_ctxs[i];
1608 		u64 cgid = ssc->cgroup_id;
1609 
1610 		if (!cgid)
1611 			continue;
1612 
1613 		cmask_copy(&qa.to_revoke_cids.mask, &ssc->prev_granted.mask);
1614 		cmask_andnot(&qa.to_revoke_cids.mask, &ssc->granted_cids.mask);
1615 		cmask_copy(&qa.to_grant_cids.mask, &ssc->granted_cids.mask);
1616 		cmask_andnot(&qa.to_grant_cids.mask, &ssc->prev_granted.mask);
1617 
1618 		scx_bpf_sub_revoke(cgid, SCX_CAP_ENQ_IMMED | SCX_CAP_PERF,
1619 				   &qa.prev_rr_cids.mask);
1620 		scx_bpf_sub_revoke(cgid, SCX_CAP_ENQ | SCX_CAP_PREEMPT |
1621 				   SCX_CAP_ENQ_IMMED | SCX_CAP_PERF,
1622 				   &qa.to_revoke_cids.mask);
1623 		scx_bpf_sub_grant(cgid, SCX_CAP_ENQ | SCX_CAP_PREEMPT |
1624 				  SCX_CAP_ENQ_IMMED | SCX_CAP_PERF,
1625 				  &qa.to_grant_cids.mask, NULL);
1626 	}
1627 
1628 	/* the current holder of the shared pool gets ENQ_IMMED on all of it */
1629 	if (nr_shared) {
1630 		s32 pos = qa.part.rr_pos;
1631 		u64 holder_cgid;
1632 
1633 		if (pos < 0 || pos >= MAX_PARTS) {
1634 			scx_bpf_error("-ERANGE");
1635 			return;
1636 		}
1637 
1638 		holder_cgid = qa.part.rr_slots[pos];	/* 0 = self, nothing to grant */
1639 		if (holder_cgid)
1640 			scx_bpf_sub_grant(holder_cgid,
1641 					  SCX_CAP_ENQ_IMMED | SCX_CAP_PERF,
1642 					  &qa.rr_cids.mask, NULL);
1643 	}
1644 }
1645 
1646 /*
1647  * Recompute the split off the node's held caps and apply it. The contexts this
1648  * runs from (the sub-sched and cgroup callbacks, the rr timer) are not
1649  * serialized by the kernel, so a single runner does the work. A caller that
1650  * finds the guard held leaves part_pending set; the holder drains it before
1651  * releasing, with the rr timer as a backstop.
1652  */
redistribute(void)1653 static void redistribute(void)
1654 {
1655 	s32 i;
1656 
1657 	__sync_fetch_and_or(&part_pending, 1);
1658 
1659 	if (!part_try_start())
1660 		return;
1661 
1662 	bpf_for(i, 0, 1024) {
1663 		__sync_fetch_and_and(&part_pending, 0);
1664 		/* charge elapsed time to the current partition before rebuilding it */
1665 		account_alloc();
1666 		compute_partition();
1667 		apply_partition();
1668 		if (!__sync_fetch_and_or(&part_pending, 0))
1669 			break;
1670 	}
1671 
1672 	part_end();
1673 }
1674 
1675 /*
1676  * Userspace pokes this (PROG_RUN) to bring alloc_ns[] current before reading
1677  * it for the stats display. Skipping when the partition guard is held is
1678  * fine - alloc_ts is untouched, so the elapsed time is charged next time.
1679  */
1680 SEC("syscall")
flush_alloc(void * ctx)1681 int flush_alloc(void *ctx)
1682 {
1683 	if (part_try_start()) {
1684 		account_alloc();
1685 		part_end();
1686 	}
1687 	return 0;
1688 }
1689 
1690 /*
1691  * Hand the shared pool to the next participant in the rotation. Self's turn
1692  * just revokes the pool back to this sched. A child's turn grants it ENQ_IMMED
1693  * on the entire pool. As only excl-held cids are time-shared, a wall-clock
1694  * rotation works. Driven by the round-robin timer.
1695  */
rr_advance(void)1696 static void rr_advance(void)
1697 {
1698 	s32 nr_shared, old_pos, new_pos;
1699 	u64 old_cgid, new_cgid;
1700 	u32 nr_rr;		/* unsigned for % */
1701 
1702 	/* a redistribute holds the partition and rebuilds the pool, so skip */
1703 	if (!part_try_start())
1704 		return;
1705 
1706 	nr_rr = qa.part.nr_rr;
1707 	nr_shared = qa.part.nr_shared;
1708 
1709 	if (nr_shared < 0 || nr_shared > MAX_PARTS) {
1710 		scx_bpf_error("-ERANGE");
1711 		return;
1712 	}
1713 
1714 	if (nr_shared && nr_rr >= 2) {
1715 		/* close out the outgoing holder's pool time */
1716 		account_alloc();
1717 
1718 		old_pos = qa.part.rr_pos;
1719 		new_pos = (old_pos + 1) % nr_rr;
1720 		old_cgid = qa.part.rr_slots[old_pos];
1721 		new_cgid = qa.part.rr_slots[new_pos];
1722 		qa.part.rr_pos = new_pos;
1723 
1724 		/*
1725 		 * Move the ENQ_IMMED cap to the next participant. The shared
1726 		 * cids stay marked CID_SHARED. qmap_dispatch() resolves the
1727 		 * live holder via rr_pos without the guard, so a dispatch
1728 		 * racing this handoff may reenqueue a task once. Harmless for a
1729 		 * time-share.
1730 		 */
1731 		if (old_cgid)
1732 			scx_bpf_sub_revoke(old_cgid,
1733 					   SCX_CAP_ENQ_IMMED | SCX_CAP_PERF,
1734 					   &qa.rr_cids.mask);
1735 		if (new_cgid)
1736 			scx_bpf_sub_grant(new_cgid,
1737 					  SCX_CAP_ENQ_IMMED | SCX_CAP_PERF,
1738 					  &qa.rr_cids.mask, NULL);
1739 	}
1740 
1741 	part_end();
1742 
1743 	/* a resplit queued while we held the guard supersedes this rotation */
1744 	if (__sync_fetch_and_or(&part_pending, 0))
1745 		redistribute();
1746 }
1747 
1748 /* advance the time-shared cid pool every round_robin_ns */
round_robin_timerfn(void * map,int * key,struct bpf_timer * timer)1749 static int round_robin_timerfn(void *map, int *key, struct bpf_timer *timer)
1750 {
1751 	rr_advance();
1752 	if (bpf_timer_start(timer, round_robin_ns, 0))
1753 		scx_bpf_error("failed to re-arm round-robin timer");
1754 	return 0;
1755 }
1756 
1757 /*
1758  * Custom cid layout for the cid-override test. On invalid input the kfunc
1759  * scx_error()s and aborts the scheduler.
1760  */
BPF_STRUCT_OPS_SLEEPABLE(qmap_init_cids)1761 s32 BPF_STRUCT_OPS_SLEEPABLE(qmap_init_cids)
1762 {
1763 	u32 nr_cpu_ids = scx_bpf_nr_cpu_ids();
1764 
1765 	if (!cid_override_mode)
1766 		return 0;
1767 
1768 	/* the arena arrays are sized SCX_QMAP_MAX_CPUS */
1769 	if (nr_cpu_ids > SCX_QMAP_MAX_CPUS) {
1770 		scx_bpf_error("nr_cpu_ids=%u exceeds SCX_QMAP_MAX_CPUS=%d",
1771 			      nr_cpu_ids, SCX_QMAP_MAX_CPUS);
1772 		return -EINVAL;
1773 	}
1774 
1775 	scx_bpf_cid_override(qa.cid_override_cpu_to_cid, nr_cpu_ids,
1776 			     qa.cid_override_shard_start, cid_override_nr_shards);
1777 	return 0;
1778 }
1779 
BPF_STRUCT_OPS_SLEEPABLE(qmap_init)1780 s32 BPF_STRUCT_OPS_SLEEPABLE(qmap_init)
1781 {
1782 	u8 __arena *slab;
1783 	u32 nr_pages, key = 0, i;
1784 	u32 nr_cids, nr_cpu_ids;
1785 	struct bpf_timer *timer;
1786 	s32 ret;
1787 
1788 	nr_cids = scx_bpf_nr_cids();
1789 	nr_cpu_ids = scx_bpf_nr_cpu_ids();
1790 
1791 	if (nr_cids > SCX_QMAP_MAX_CPUS) {
1792 		scx_bpf_error("nr_cids=%u exceeds SCX_QMAP_MAX_CPUS=%d",
1793 			      nr_cids, SCX_QMAP_MAX_CPUS);
1794 		return -EINVAL;
1795 	}
1796 	if (nr_cpu_ids > SCX_QMAP_MAX_CPUS) {
1797 		scx_bpf_error("nr_cpu_ids=%u exceeds SCX_QMAP_MAX_CPUS=%d",
1798 			      nr_cpu_ids, SCX_QMAP_MAX_CPUS);
1799 		return -EINVAL;
1800 	}
1801 
1802 	/*
1803 	 * Allocate the task_ctx slab in arena and thread the entire slab onto
1804 	 * the free list. max_tasks is set by userspace before load. Each entry
1805 	 * is TASK_CTX_STRIDE bytes - task_ctx's trailing cpus_allowed flex
1806 	 * array extends into the stride tail.
1807 	 */
1808 	if (!max_tasks) {
1809 		scx_bpf_error("max_tasks must be > 0");
1810 		return -EINVAL;
1811 	}
1812 
1813 	nr_pages = (max_tasks * TASK_CTX_STRIDE + PAGE_SIZE - 1) / PAGE_SIZE;
1814 	slab = bpf_arena_alloc_pages(&arena, NULL, nr_pages, NUMA_NO_NODE, 0);
1815 	if (!slab) {
1816 		scx_bpf_error("failed to allocate task_ctx slab");
1817 		return -ENOMEM;
1818 	}
1819 	qa.task_ctxs = (task_ctx_t *)slab;
1820 
1821 	bpf_for(i, 0, 5)
1822 		qa.fifos[i].idx = i;
1823 
1824 	bpf_for(i, 0, max_tasks) {
1825 		task_ctx_t *cur = (task_ctx_t *)(slab + i * TASK_CTX_STRIDE);
1826 		task_ctx_t *next = (i + 1 < max_tasks) ?
1827 			(task_ctx_t *)(slab + (i + 1) * TASK_CTX_STRIDE) : NULL;
1828 		cur->next_free = next;
1829 	}
1830 	qa.task_free_head = (task_ctx_t *)slab;
1831 
1832 	/* cache the cid count, trusted to be <= SCX_QMAP_MAX_CPUS hereafter */
1833 	qa.nr_cids = nr_cids;
1834 
1835 	/* cmasks are embedded in qa, so they only need initializing */
1836 	cmask_init(&qa.idle_cids.mask, 0, nr_cids);
1837 	cmask_init(&qa.rr_cids.mask, 0, nr_cids);
1838 	cmask_init(&qa.prev_rr_cids.mask, 0, nr_cids);
1839 	cmask_init(&qa.self_cids.mask, 0, nr_cids);
1840 	cmask_init(&qa.to_revoke_cids.mask, 0, nr_cids);
1841 	cmask_init(&qa.to_grant_cids.mask, 0, nr_cids);
1842 	cmask_init(&qa.held_excl.mask, 0, nr_cids);
1843 	cmask_init(&qa.held_shared.mask, 0, nr_cids);
1844 
1845 	scx_bpf_sub_caps(0, SCX_CAP_ENQ, &qa.held_excl.mask);
1846 	scx_bpf_sub_caps(0, SCX_CAP_ENQ_IMMED, &qa.held_shared.mask);
1847 	cmask_andnot(&qa.held_shared.mask, &qa.held_excl.mask);
1848 
1849 	bpf_for(i, 0, MAX_SUB_SCHEDS) {
1850 		cmask_init(&qa.sub_sched_ctxs[i].granted_cids.mask, 0, nr_cids);
1851 		cmask_init(&qa.sub_sched_ctxs[i].prev_granted.mask, 0, nr_cids);
1852 	}
1853 
1854 	/*
1855 	 * The root starts holding every cid. qmap_sub_ecaps_updated() maintains
1856 	 * per-cid shared state as effective caps settle, and redistribute()
1857 	 * rebuilds owner and self from held caps. A non-root node starts with
1858 	 * nothing.
1859 	 */
1860 	bpf_for(i, 0, nr_cids) {
1861 		if (!sub_cgroup_id) {
1862 			cmask_set(i, &qa.self_cids.mask);
1863 			qa.part.cid_owner[i] = CID_SELF;
1864 		} else {
1865 			qa.part.cid_owner[i] = CID_NONE;
1866 		}
1867 	}
1868 	qa.part.nr_shared = 0;
1869 
1870 	ret = scx_bpf_create_dsq(SHARED_DSQ, -1);
1871 	if (ret) {
1872 		scx_bpf_error("failed to create DSQ %d (%d)", SHARED_DSQ, ret);
1873 		return ret;
1874 	}
1875 
1876 	ret = scx_bpf_create_dsq(HIGHPRI_DSQ, -1);
1877 	if (ret) {
1878 		scx_bpf_error("failed to create DSQ %d (%d)", HIGHPRI_DSQ, ret);
1879 		return ret;
1880 	}
1881 
1882 	ret = scx_bpf_create_dsq(LOWPRI_DSQ, -1);
1883 	if (ret)
1884 		return ret;
1885 
1886 	timer = bpf_map_lookup_elem(&monitor_timer, &key);
1887 	if (!timer)
1888 		return -ESRCH;
1889 	bpf_timer_init(timer, &monitor_timer, CLOCK_MONOTONIC);
1890 	bpf_timer_set_callback(timer, monitor_timerfn);
1891 	ret = bpf_timer_start(timer, ONE_SEC_IN_NS, 0);
1892 	if (ret)
1893 		return ret;
1894 
1895 	if (__COMPAT_has_generic_reenq()) {
1896 		/* see lowpri_timerfn() */
1897 		timer = bpf_map_lookup_elem(&lowpri_timer, &key);
1898 		if (!timer)
1899 			return -ESRCH;
1900 		bpf_timer_init(timer, &lowpri_timer, CLOCK_MONOTONIC);
1901 		bpf_timer_set_callback(timer, lowpri_timerfn);
1902 		ret = bpf_timer_start(timer, LOWPRI_INTV_NS, 0);
1903 		if (ret)
1904 			return ret;
1905 	}
1906 
1907 	/* sub-sched: drive the boundary-cid round-robin from a bpf timer */
1908 	timer = bpf_map_lookup_elem(&round_robin_timer, &key);
1909 	if (!timer)
1910 		return -ESRCH;
1911 	bpf_timer_init(timer, &round_robin_timer, CLOCK_MONOTONIC);
1912 	bpf_timer_set_callback(timer, round_robin_timerfn);
1913 	ret = bpf_timer_start(timer, round_robin_ns, 0);
1914 	if (ret)
1915 		return ret;
1916 
1917 	return 0;
1918 }
1919 
BPF_STRUCT_OPS(qmap_exit,struct scx_exit_info * ei)1920 void BPF_STRUCT_OPS(qmap_exit, struct scx_exit_info *ei)
1921 {
1922 	UEI_RECORD(uei, ei);
1923 }
1924 
1925 /*
1926  * Seed a new sub slot with the cgroup's current weight. The kernel delivers
1927  * ops.cpuctl_set_weight() only on value-changing writes, so a weight set
1928  * before the sub attached would otherwise go unnoticed.
1929  */
cgrp_cur_weight(u64 cgid)1930 static u32 cgrp_cur_weight(u64 cgid)
1931 {
1932 	struct cgroup_subsys_state *css;
1933 	struct cgroup *cgrp;
1934 	u32 weight = 100;
1935 
1936 	cgrp = bpf_cgroup_from_id(cgid);
1937 	if (!cgrp)
1938 		return weight;
1939 
1940 	css = BPF_CORE_READ(cgrp, subsys[cpu_cgrp_id]);
1941 	if (css) {
1942 		struct task_group *tg = container_of(css, struct task_group, css);
1943 		u32 w = BPF_CORE_READ(tg, scx.weight);
1944 
1945 		if (w)
1946 			weight = w;
1947 	}
1948 	bpf_cgroup_release(cgrp);
1949 	return weight;
1950 }
1951 
BPF_STRUCT_OPS(qmap_sub_attach,struct scx_sub_attach_args * args)1952 s32 BPF_STRUCT_OPS(qmap_sub_attach, struct scx_sub_attach_args *args)
1953 {
1954 	s32 i;
1955 
1956 	/* as long as there is at least one excl cpu, children can attach */
1957 	if (!cmask_weight(&qa.held_excl.mask))
1958 		return -ENOSPC;
1959 
1960 	for (i = 0; i < MAX_SUB_SCHEDS; i++) {
1961 		if (qa.sub_sched_ctxs[i].cgroup_id)
1962 			continue;
1963 
1964 		qa.sub_sched_ctxs[i].cgroup_id = args->ops->sub_cgroup_id;
1965 		qa.sub_sched_ctxs[i].weight = cgrp_cur_weight(args->ops->sub_cgroup_id);
1966 		qa.nr_sub_scheds++;
1967 		bpf_printk("attaching sub-sched[%d] on %s", i, args->cgroup_path);
1968 		redistribute();
1969 		return 0;
1970 	}
1971 
1972 	return -ENOSPC;
1973 }
1974 
BPF_STRUCT_OPS(qmap_sub_detach,struct scx_sub_detach_args * args)1975 void BPF_STRUCT_OPS(qmap_sub_detach, struct scx_sub_detach_args *args)
1976 {
1977 	s32 i;
1978 
1979 	for (i = 0; i < MAX_SUB_SCHEDS; i++) {
1980 		if (qa.sub_sched_ctxs[i].cgroup_id != args->ops->sub_cgroup_id)
1981 			continue;
1982 
1983 		qa.sub_sched_ctxs[i].cgroup_id = 0;
1984 		qa.sub_sched_ctxs[i].weight = 100;
1985 		cmask_init(&qa.sub_sched_ctxs[i].granted_cids.mask, 0, qa.nr_cids);
1986 		qa.nr_sub_scheds--;
1987 		bpf_printk("detaching sub-sched[%d] on %s", i, args->cgroup_path);
1988 		redistribute();
1989 		break;
1990 	}
1991 }
1992 
BPF_STRUCT_OPS(qmap_sub_caps_updated,const struct scx_cmask * cmask,u64 caps)1993 void BPF_STRUCT_OPS(qmap_sub_caps_updated, const struct scx_cmask *cmask, u64 caps)
1994 {
1995 	/* our held caps changed, redistribute */
1996 	redistribute();
1997 }
1998 
BPF_STRUCT_OPS(qmap_sub_ecaps_updated,s32 cid,u64 before,u64 after)1999 void BPF_STRUCT_OPS(qmap_sub_ecaps_updated, s32 cid, u64 before, u64 after)
2000 {
2001 	/*
2002 	 * Effective caps updated. Track which cids hold shared caps so a self
2003 	 * task placed there enqueues IMMED.
2004 	 */
2005 	if (after & SCX_CAP_ENQ_IMMED)
2006 		qa.cid_shared[cid] = (after & SCX_CAP_ENQ) ? 0 : 1;
2007 	else
2008 		qa.cid_shared[cid] = 0;
2009 }
2010 
2011 SCX_OPS_CID_DEFINE(qmap_ops,
2012 	       .flags			= SCX_OPS_ENQ_EXITING | SCX_OPS_TID_TO_TASK,
2013 	       .select_cid		= (void *)qmap_select_cid,
2014 	       .enqueue			= (void *)qmap_enqueue,
2015 	       .dequeue			= (void *)qmap_dequeue,
2016 	       .dispatch		= (void *)qmap_dispatch,
2017 	       .tick			= (void *)qmap_tick,
2018 	       .core_sched_before	= (void *)qmap_core_sched_before,
2019 	       .set_cmask		= (void *)qmap_set_cmask,
2020 	       .update_idle		= (void *)qmap_update_idle,
2021 	       .init_task		= (void *)qmap_init_task,
2022 	       .exit_task		= (void *)qmap_exit_task,
2023 	       .dump			= (void *)qmap_dump,
2024 	       .dump_cid		= (void *)qmap_dump_cid,
2025 	       .dump_task		= (void *)qmap_dump_task,
2026 	       .cpuctl_init		= (void *)qmap_cpuctl_init,
2027 	       .cpuctl_set_weight	= (void *)qmap_cpuctl_set_weight,
2028 	       .cpuctl_set_bandwidth	= (void *)qmap_cpuctl_set_bandwidth,
2029 	       .cpuctl_move		= (void *)qmap_cpuctl_move,
2030 	       .sub_attach		= (void *)qmap_sub_attach,
2031 	       .sub_detach		= (void *)qmap_sub_detach,
2032 	       .sub_caps_updated	= (void *)qmap_sub_caps_updated,
2033 	       .sub_ecaps_updated	= (void *)qmap_sub_ecaps_updated,
2034 	       .init_cids		= (void *)qmap_init_cids,
2035 	       .init			= (void *)qmap_init,
2036 	       .exit			= (void *)qmap_exit,
2037 	       .timeout_ms		= 5000U,
2038 	       .name			= "qmap");
2039