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
spin_ms(int ms)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
pin(pid_t pid,int idx)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 */
child(int idx)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, ¶m))
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
run(void * ctx)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