1#ifdef TEST_SCHED
2
3#include <mem/alloc_or_die.h>
4#include <sch/sched.h>
5#include <string.h>
6#include <test.h>
7#include <thread/apc.h>
8#include <thread/daemon.h>
9#include <thread/reaper.h>
10#include <thread/thread.h>
11#include <thread/workqueue.h>
12#include <time/spin_sleep.h>
13
14static atomic_bool workqueue_ran = false;
15static _Atomic uint32_t workqueue_times = 0;
16static void workqueue_fn(void *arg, void *unused) {
17 (void) arg, (void) unused;
18 atomic_store(&workqueue_ran, true);
19 atomic_fetch_add(&workqueue_times, 1);
20}
21
22TEST_DECLARE(workqueue_test, .tier = TEST_TIER_UNIT) {
23 uint64_t tsc = rdtsc();
24 uint64_t times = 256;
25
26 for (uint64_t i = 0; i < times; i++) {
27 enum workqueue_error err =
28 workqueue_add_fast_oneshot(func: workqueue_fn, WORK_ARGS(NULL, NULL));
29 (void) err;
30 }
31
32 uint64_t total = rdtsc() - tsc;
33 sleep_spin_ms(msec: 50);
34
35 while (!atomic_load(&workqueue_ran))
36 cpu_relax();
37
38 char *msg = kmalloc(100, ALLOC_FLAGS_ZERO);
39 TEST_ASSERT(msg);
40 snprintf(buffer: msg, buffer_len: 100, format: "Took %d clock cycles to add to event pool %d times",
41 total, times);
42 test_info(msg);
43
44 TEST_ASSERT(atomic_load(&workqueue_ran));
45
46 msg = kmalloc(100, ALLOC_FLAGS_ZERO);
47 snprintf(buffer: msg, buffer_len: 100,
48 format: "Event pool ran %d times, tests should've had it run %d times",
49 workqueue_times, times);
50 test_info(msg);
51
52 return TEST_SUCCESS;
53}
54
55static void sleepy_entry(void *) {
56 thread_sleep_for_ms(ms: 9000);
57 thread_print(t: thread_get_current());
58}
59
60TEST_DECLARE(sched_sleepy_test, .tier = TEST_TIER_UNIT) {
61 thread_spawn(name: "sched_sleepy_test", entry: sleepy_entry, NULL);
62 return TEST_SUCCESS;
63}
64
65#define WQ_2_TIMES 4096
66#define WQ_2_THREADS 2
67
68static _Atomic uint32_t times_2 = 0;
69
70static void wq_test_2(void *a, void *b) {
71 (void) a, (void) b;
72 atomic_fetch_add(&times_2, 1);
73 for (uint64_t i = 0; i < 500; i++)
74 cpu_relax();
75}
76
77static struct workqueue *wq = NULL;
78static _Atomic uint32_t threads_left = WQ_2_THREADS;
79
80static void enqueue_thread(void *) {
81 for (size_t i = 0; i < WQ_2_TIMES / WQ_2_THREADS; i++) {
82 for (uint64_t i = 0; i < 500; i++)
83 cpu_relax();
84
85 workqueue_enqueue_oneshot(queue: wq, func: wq_test_2, WORK_ARGS(NULL, wq));
86 scheduler_yield();
87 }
88 atomic_fetch_sub(&threads_left, 1);
89}
90
91TEST_DECLARE(workqueue_test_2, .tier = TEST_TIER_UNIT) {
92 struct cpu_mask mask;
93 alloc_or_die(cpu_mask_init(&mask, global.core_count));
94
95 cpu_mask_set_all(m: &mask);
96
97 struct workqueue_attributes attrs = {
98 .capacity = WQ_2_TIMES,
99 .flags = WORKQUEUE_FLAG_AUTO_SPAWN | WORKQUEUE_FLAG_ON_DEMAND,
100 .spawn_delay = 1,
101 .idle_check.max = 10000,
102 .idle_check.min = 2000,
103 .min_workers = 2,
104 .max_workers = 64,
105 .worker_cpu_mask = mask,
106 };
107
108 wq = workqueue_create(NULL, attrs: &attrs);
109
110 for (size_t i = 0; i < WQ_2_THREADS; i++) {
111 test_info("spawning workqueue enqueue threads");
112 thread_spawn(name: "workqueue_enqueue_thread", entry: enqueue_thread, NULL);
113 }
114
115 test_info("yielding");
116 thread_apply_cpu_penalty(t: thread_get_current());
117 while (atomic_load(&threads_left) > 0) {
118 scheduler_yield();
119 }
120
121 uint64_t workers = wq->num_workers;
122
123 char *msg = kmalloc(100);
124 snprintf(buffer: msg, buffer_len: 100, format: "There are %d workers", workers);
125 test_info(msg);
126
127 test_info("destroy");
128 workqueue_destroy(queue: wq);
129 return TEST_SUCCESS;
130}
131
132static atomic_bool daemon_work_run = false;
133static enum daemon_thread_command daemon_work(void *a, void *b) {
134 atomic_store(&daemon_work_run, true);
135 return DAEMON_THREAD_COMMAND_SLEEP;
136}
137
138static struct daemon_work dwork =
139 DAEMON_WORK_FROM(daemon_work, WORK_ARGS(NULL, NULL));
140
141TEST_DECLARE(daemon_test, .tier = TEST_TIER_UNIT) {
142 struct cpu_mask cmask;
143 cpu_mask_init(m: &cmask, nbits: global.core_count);
144 cpu_mask_set_all(m: &cmask);
145
146 struct daemon_attributes attrs = {
147 .max_timesharing_threads = 67,
148 .flags = DAEMON_FLAG_AUTO_SPAWN | DAEMON_FLAG_HAS_NAME,
149 .thread_cpu_mask = cmask,
150 .min_timesharing_threads = 4,
151 };
152
153 struct daemon *daemon =
154 daemon_create(fmt: "daemon_test", attrs: &attrs, timesharing_work: &dwork, NULL, NULL);
155
156 kassert(daemon);
157
158 daemon_wake_timesharing_worker(daemon);
159 while (!atomic_load(&daemon_work_run))
160 scheduler_yield();
161
162 daemon_destroy(daemon);
163 return TEST_SUCCESS;
164}
165
166static atomic_bool si_apc_ran = false;
167static struct thread *si_t;
168static atomic_bool si_ok = false;
169static atomic_bool si_started = false;
170
171static void apc_si(void *apc) {
172 atomic_store(&si_apc_ran, true);
173}
174
175static void apc_enqueue_thread(void *) {
176 struct apc *apc = apc_create();
177 apc_init(a: apc, fn: apc_si, NULL);
178
179 while (!atomic_load(&si_started))
180 cpu_relax();
181
182 apc_enqueue(t: si_t, a: apc, type: APC_TYPE_KERNEL);
183}
184
185static void sleeping_thread(void *) {
186 atomic_store(&si_started, true);
187
188 thread_prepare_to_sleep(t: thread_get_current(), r: THREAD_SLEEP_REASON_MANUAL,
189 wait_type: THREAD_WAIT_INTERRUPTIBLE, expect_wake_src: (void *) 4);
190
191 thread_yield_until_wake_match();
192
193 atomic_store(&si_ok, true);
194}
195
196static void waking_thread(void *) {
197 while (!atomic_load(&si_apc_ran))
198 scheduler_yield();
199
200 thread_wake(t: si_t, reason: THREAD_WAKE_REASON_SLEEP_MANUAL,
201 prio: si_t->perceived_prio_class, wake_src: (void *) 4);
202}
203
204TEST_DECLARE(thread_sleep_interruptible_test, .tier = TEST_TIER_INTEGRATION) {
205 if (global.core_count < 4) {
206 test_info("too few cores");
207 return TEST_SKIP(TEST_SKIP_NONE);
208 }
209
210 si_t = thread_spawn_on_core(name: "si_thread", entry: sleeping_thread, NULL, core_id: 1);
211 thread_spawn_on_core(name: "si_wake", entry: waking_thread, NULL, core_id: 2);
212 thread_spawn_on_core(name: "si_apc_e", entry: apc_enqueue_thread, NULL, core_id: 3);
213 while (!atomic_load(&si_ok))
214 scheduler_yield();
215
216 return TEST_SUCCESS;
217}
218
219static atomic_bool gogo = false;
220static atomic_bool eq = false;
221
222static void dpc_idle(struct dpc *dpc, void *ctx) {
223 (void) dpc, (void) ctx;
224
225 atomic_store(&gogo, true);
226 kassert(scheduler_core_idle(smp_core()));
227}
228
229static void dpc_on_event_dummy_thread(void *a) {
230 (void) a;
231 while (!atomic_load(&eq))
232 cpu_relax();
233
234 for (size_t i = 0; i < 5000; i++)
235 scheduler_yield();
236
237 kassert(!atomic_load(&gogo));
238}
239
240/* we put a thread on a core that is not idle, enqueue a DPC over
241 * there, trigger some reschedules, and then verify that the DPC
242 * only ever runs once the core actually goes idle */
243TEST_DECLARE(dpc_on_event_test, .tier = TEST_TIER_UNIT) {
244 size_t i;
245 size_t found = SIZE_MAX;
246 for_each_cpu_id(i) {
247 if (scheduler_core_idle(c: global.cores[i])) {
248 found = i;
249 break;
250 }
251 }
252
253 if (found == SIZE_MAX) {
254 test_info("Could not find idle CPU");
255 return TEST_SKIP(TEST_SKIP_NONE);
256 }
257
258 struct thread *t =
259 thread_create(name: "dpc_dummy", entry_point: dpc_on_event_dummy_thread, NULL);
260 t->flags |= THREAD_FLAG_PINNED;
261 thread_enqueue_on_core(t, core_id: found);
262
263 /* we now know the other processor is in the thread */
264 struct dpc *dp = dpc_create(fn: dpc_idle, NULL);
265 dpc_enqueue_on_cpu(cpu: found, d: dp, e: DPC_CPU_IDLE);
266 atomic_store(&eq, true);
267 while (!atomic_load(&gogo))
268 cpu_relax();
269
270 return TEST_SUCCESS;
271}
272
273#define SCHED_PUSH_TEST_THREADS 256
274
275static atomic_uint left = SCHED_PUSH_TEST_THREADS;
276static atomic_bool at_least_one_migrated = false;
277
278static void sched_push_try(void *) {
279 while (smp_core_id() == 0 && !atomic_load(&at_least_one_migrated))
280 scheduler_yield();
281
282 atomic_fetch_sub(&left, 1);
283 atomic_store(&at_least_one_migrated, true);
284}
285
286TEST_DECLARE(sched_push_target_test, .tier = TEST_TIER_INTEGRATION) {
287 test_info("This test takes a bit. uncomment me to run it");
288 return TEST_SKIP(TEST_SKIP_NONE);
289 enum irql irql = irql_raise(new_level: IRQL_DISPATCH_LEVEL);
290 for (size_t i = 0; i < SCHED_PUSH_TEST_THREADS; i++) {
291 thread_spawn_on_core(name: "push_test_%zu", entry: sched_push_try, NULL, core_id: 0, i);
292 }
293 irql_lower(old_level: irql);
294
295 while (!atomic_load(&left))
296 scheduler_yield();
297
298 return TEST_SUCCESS;
299}
300
301#endif
302