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