xref: /linux/tools/testing/selftests/sched_ext/enable_cmask.c (revision d781d1b78acf547bafeb1592e8ba4020dce515c6)
1 // SPDX-License-Identifier: GPL-2.0
2 /* Copyright (c) 2026 Tejun Heo <tj@kernel.org> */
3 #define _GNU_SOURCE
4 #include <sched.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <time.h>
8 #include <unistd.h>
9 #include <sys/wait.h>
10 #include <bpf/bpf.h>
11 #include <scx/common.h>
12 #include "enable_cmask.bpf.skel.h"
13 #include "scx_test.h"
14 
15 #define SCHED_EXT 7
16 #define NR_CHILDREN 8
17 #define MAX_CPUS 1024
18 
19 static int cpus[MAX_CPUS];
20 static int nr_cpus;
21 
22 static void spin_ms(int ms)
23 {
24 	struct timespec start, now;
25 
26 	clock_gettime(CLOCK_MONOTONIC, &start);
27 	do {
28 		clock_gettime(CLOCK_MONOTONIC, &now);
29 	} while ((now.tv_sec - start.tv_sec) * 1000 +
30 		 (now.tv_nsec - start.tv_nsec) / 1000000 < ms);
31 }
32 
33 static int pin(pid_t pid, int idx)
34 {
35 	cpu_set_t set;
36 
37 	CPU_ZERO(&set);
38 	CPU_SET(cpus[idx % nr_cpus], &set);
39 	return sched_setaffinity(pid, sizeof(set), &set);
40 }
41 
42 /*
43  * Pin, switch to SCHED_EXT for a class-switch enable, fork a grandchild that
44  * inherits the policy for a fork-path enable, then change affinity a few times
45  * while running for set_cmask() on live tasks.
46  */
47 static int child(int idx)
48 {
49 	struct sched_param param = {};
50 	int i, status;
51 	pid_t pid;
52 
53 	if (pin(0, idx) || sched_setscheduler(0, SCHED_EXT, &param))
54 		return 1;
55 
56 	pid = fork();
57 	if (pid < 0)
58 		return 1;
59 	if (!pid) {
60 		spin_ms(20);
61 		return 0;
62 	}
63 
64 	for (i = 1; i <= 4; i++) {
65 		if (pin(0, idx + i))
66 			return 1;
67 		spin_ms(5);
68 	}
69 
70 	return waitpid(pid, &status, 0) == pid && !status ? 0 : 1;
71 }
72 
73 static enum scx_test_status run(void *ctx)
74 {
75 	struct enable_cmask *skel;
76 	struct bpf_link *link;
77 	pid_t pids[NR_CHILDREN];
78 	cpu_set_t set;
79 	int i, status, failed = 0;
80 
81 	if (!__COMPAT_struct_has_field("scx_enable_args", "cmask_arena_addr"))
82 		return SCX_TEST_SKIP;
83 
84 	SCX_FAIL_IF(sched_getaffinity(0, sizeof(set), &set), "Failed to read affinity");
85 	for (i = 0; i < MAX_CPUS && i < CPU_SETSIZE; i++)
86 		if (CPU_ISSET(i, &set))
87 			cpus[nr_cpus++] = i;
88 	if (nr_cpus < 2)
89 		return SCX_TEST_SKIP;
90 
91 	skel = enable_cmask__open();
92 	SCX_FAIL_IF(!skel, "Failed to open");
93 	SCX_ENUM_INIT(skel);
94 	SCX_FAIL_IF(enable_cmask__load(skel), "Failed to load skel");
95 
96 	link = bpf_map__attach_struct_ops(skel->maps.enable_cmask_ops);
97 	SCX_FAIL_IF(!link, "Failed to attach struct_ops");
98 
99 	for (i = 0; i < NR_CHILDREN; i++) {
100 		pids[i] = fork();
101 		SCX_FAIL_IF(pids[i] < 0, "Failed to fork");
102 		if (!pids[i])
103 			exit(child(i));
104 	}
105 
106 	/* affinity changes from the outside race with the children's own */
107 	for (i = 0; i < NR_CHILDREN; i++)
108 		pin(pids[i], i + NR_CHILDREN);
109 
110 	for (i = 0; i < NR_CHILDREN; i++) {
111 		if (waitpid(pids[i], &status, 0) != pids[i] || status)
112 			failed++;
113 	}
114 
115 	bpf_link__destroy(link);
116 
117 	SCX_EQ(skel->data->uei.kind, EXIT_KIND(SCX_EXIT_UNREG));
118 	SCX_EQ(failed, 0);
119 	SCX_GE(skel->bss->nr_enable, 2 * NR_CHILDREN);
120 	SCX_EQ(skel->bss->nr_initial_set_cmask, skel->bss->nr_enable);
121 	SCX_GT(skel->bss->nr_set_cmask, skel->bss->nr_initial_set_cmask);
122 	SCX_GE(skel->bss->nr_set_weight, skel->bss->nr_enable);
123 	printf("enable=%lu initial_set_cmask=%lu set_cmask=%lu set_weight=%lu\n",
124 	       (unsigned long)skel->bss->nr_enable,
125 	       (unsigned long)skel->bss->nr_initial_set_cmask,
126 	       (unsigned long)skel->bss->nr_set_cmask,
127 	       (unsigned long)skel->bss->nr_set_weight);
128 
129 	enable_cmask__destroy(skel);
130 	return SCX_TEST_PASS;
131 }
132 
133 struct scx_test enable_cmask = {
134 	.name = "enable_cmask",
135 	.description = "Check the cid-form ops.enable() cmask and the set_cmask() after it",
136 	.run = run,
137 };
138 REGISTER_SCX_TEST(&enable_cmask)
139