1#include "thread/tests/test_internal.h"
2
3TEST_GROUP_DECLARE(apc, .intensity_desc = {
4 .curve = SCALE_PIECEWISE_LOG,
5 .unit = "iters",
6 });
7
8static atomic_bool apc_ran = false;
9static atomic_uint apc_destroyed = 0;
10
11static void the_apc(void *) {
12 atomic_store(&apc_ran, true);
13}
14
15static void the_apc_destroy(struct apc *apc) {
16 atomic_fetch_add(&apc_destroyed, 1);
17 kfree(apc);
18}
19
20static void apc_thread(void *) {
21 while (!atomic_load(&apc_ran))
22 cpu_relax();
23}
24
25static struct thread *ted = NULL;
26TEST_DECLARE_INTEGRATION(apc, delivery) {
27 atomic_store(&apc_ran, false);
28 atomic_store(&apc_destroyed, 0);
29 ted = thread_spawn_joinable(name: "apc_test_thread", entry: apc_thread, NULL);
30 struct apc *a = kmalloc(sizeof(struct apc), ALLOC_FLAGS_ZERO);
31 if (!a || !ted) {
32 if (a)
33 kfree(a);
34 if (ted)
35 thread_detach(t: ted);
36
37 return TEST_FAIL("allocation failed");
38 }
39
40 apc_init(a, fn: the_apc, NULL, destroy: the_apc_destroy);
41
42 TEST_ASSERT(thread_get(ted));
43 TEST_ASSERT(apc_enqueue(ted, a, APC_TYPE_KERNEL));
44 thread_put(t: ted);
45 apc_put(a);
46
47 /* the thread only returns once it has seen the APC run */
48 thread_join(t: ted);
49 TEST_ASSERT(atomic_load(&apc_ran));
50 TEST_ASSERT_EQ(atomic_load(&apc_destroyed), 1);
51
52 return TEST_SUCCESS;
53}
54
55static atomic_uint apc_ref_destroyed = 0;
56
57static void apc_ref_destroy(struct apc *apc) {
58 (void) apc;
59 atomic_fetch_add(&apc_ref_destroyed, 1);
60}
61
62TEST_DECLARE_INTEGRATION(apc, refcount_finalizes_at_zero) {
63 struct apc apc;
64 atomic_store(&apc_ref_destroyed, 0);
65 apc_init(a: &apc, fn: the_apc, NULL, destroy: apc_ref_destroy);
66
67 TEST_ASSERT(apc_get(&apc));
68 apc_put(a: &apc);
69 TEST_ASSERT_EQ(atomic_load(&apc_ref_destroyed), 0);
70 apc_put(a: &apc);
71 TEST_ASSERT_EQ(atomic_load(&apc_ref_destroyed), 1);
72 return TEST_SUCCESS;
73}
74
75TEST_DECLARE_INTEGRATION(apc, null_destroy_valid) {
76 struct apc apc;
77 apc_init(a: &apc, fn: the_apc, NULL, NULL);
78 apc_put(a: &apc);
79 return TEST_SUCCESS;
80}
81
82static atomic_bool apc_cancel_ready = false;
83static atomic_bool apc_cancel_release = false;
84static atomic_bool apc_cancel_ran = false;
85static atomic_uint apc_cancel_destroyed = 0;
86
87static void cancelled_apc(void *arg) {
88 (void) arg;
89 atomic_store(&apc_cancel_ran, true);
90}
91
92static void cancelled_apc_destroy(struct apc *apc) {
93 atomic_fetch_add(&apc_cancel_destroyed, 1);
94 kfree(apc);
95}
96
97static void apc_cancel_target(void *arg) {
98 (void) arg;
99 apc_disable_kernel();
100 atomic_store(&apc_cancel_ready, true);
101 while (!atomic_load(&apc_cancel_release))
102 scheduler_yield();
103}
104
105TEST_DECLARE_INTEGRATION(apc, cancel_releases_queue_ref) {
106 atomic_store(&apc_cancel_ready, false);
107 atomic_store(&apc_cancel_release, false);
108 atomic_store(&apc_cancel_ran, false);
109 atomic_store(&apc_cancel_destroyed, 0);
110
111 struct thread *target =
112 thread_spawn_joinable(name: "apc_cancel_target", entry: apc_cancel_target, NULL);
113 TEST_ASSERT_NONNULL(target);
114 while (!atomic_load(&apc_cancel_ready))
115 scheduler_yield();
116
117 struct apc *apc = apc_create();
118 TEST_ASSERT_NONNULL(apc);
119 apc_init(a: apc, fn: cancelled_apc, NULL, destroy: cancelled_apc_destroy);
120
121 TEST_ASSERT(thread_get(target));
122 TEST_ASSERT(apc_enqueue(target, apc, APC_TYPE_KERNEL));
123 TEST_ASSERT(!apc_enqueue(target, apc, APC_TYPE_KERNEL));
124 TEST_ASSERT(apc_cancel(target, apc));
125 thread_put(t: target);
126
127 apc_put(a: apc);
128 TEST_ASSERT_EQ(atomic_load(&apc_cancel_destroyed), 1);
129 TEST_ASSERT(!atomic_load(&apc_cancel_ran));
130
131 atomic_store(&apc_cancel_release, true);
132 thread_join(t: target);
133 return TEST_SUCCESS;
134}
135
136static atomic_bool apc_rundown_ready = false;
137static atomic_bool apc_rundown_release = false;
138static atomic_bool apc_rundown_ran = false;
139static atomic_uint apc_rundown_destroyed = 0;
140
141static void rundown_apc(void *arg) {
142 (void) arg;
143 atomic_store(&apc_rundown_ran, true);
144}
145
146static void rundown_apc_destroy(struct apc *apc) {
147 atomic_fetch_add(&apc_rundown_destroyed, 1);
148 kfree(apc);
149}
150
151static void apc_rundown_target(void *arg) {
152 (void) arg;
153 apc_disable_kernel();
154 atomic_store(&apc_rundown_ready, true);
155 while (!atomic_load(&apc_rundown_release))
156 scheduler_yield();
157}
158
159TEST_DECLARE_INTEGRATION(apc, thread_rundown_releases_queue_ref) {
160 atomic_store(&apc_rundown_ready, false);
161 atomic_store(&apc_rundown_release, false);
162 atomic_store(&apc_rundown_ran, false);
163 atomic_store(&apc_rundown_destroyed, 0);
164
165 struct thread *target =
166 thread_spawn_joinable(name: "apc_rundown_target", entry: apc_rundown_target, NULL);
167 TEST_ASSERT_NONNULL(target);
168 while (!atomic_load(&apc_rundown_ready))
169 scheduler_yield();
170
171 struct apc *apc = apc_create();
172 TEST_ASSERT_NONNULL(apc);
173 apc_init(a: apc, fn: rundown_apc, NULL, destroy: rundown_apc_destroy);
174
175 TEST_ASSERT(thread_get(target));
176 TEST_ASSERT(apc_enqueue(target, apc, APC_TYPE_KERNEL));
177 thread_put(t: target);
178 apc_put(a: apc);
179
180 atomic_store(&apc_rundown_release, true);
181 thread_join(t: target);
182
183 TEST_ASSERT(!atomic_load(&apc_rundown_ran));
184 TEST_ASSERT_EQ(atomic_load(&apc_rundown_destroyed), 1);
185 return TEST_SUCCESS;
186}
187
188static atomic_uint apc_reuse_ran = 0;
189static atomic_uint apc_reuse_destroyed = 0;
190
191static void reused_apc(void *arg) {
192 (void) arg;
193 atomic_fetch_add(&apc_reuse_ran, 1);
194}
195
196static void reused_apc_destroy(struct apc *apc) {
197 (void) apc;
198 atomic_fetch_add(&apc_reuse_destroyed, 1);
199}
200
201static void apc_reuse_target(void *arg) {
202 (void) arg;
203 while (atomic_load(&apc_reuse_ran) < 2)
204 scheduler_yield();
205}
206
207TEST_DECLARE_INTEGRATION(apc, caller_ref_allows_reuse) {
208 atomic_store(&apc_reuse_ran, 0);
209 atomic_store(&apc_reuse_destroyed, 0);
210
211 struct thread *target =
212 thread_spawn_joinable(name: "apc_reuse_target", entry: apc_reuse_target, NULL);
213 TEST_ASSERT_NONNULL(target);
214
215 struct apc apc;
216 apc_init(a: &apc, fn: reused_apc, NULL, destroy: reused_apc_destroy);
217 for (size_t expected = 1; expected <= 2; expected++) {
218 TEST_ASSERT(thread_get(target));
219 TEST_ASSERT(apc_enqueue(target, &apc, APC_TYPE_KERNEL));
220 thread_put(t: target);
221 while (atomic_load(&apc_reuse_ran) < expected)
222 scheduler_yield();
223 while (atomic_load_explicit(&apc.state, memory_order_acquire) !=
224 APC_STATE_IDLE)
225 scheduler_yield();
226 }
227
228 thread_join(t: target);
229 TEST_ASSERT_EQ(atomic_load(&apc_reuse_ran), 2);
230 TEST_ASSERT_EQ(atomic_load(&apc_reuse_destroyed), 0);
231 apc_put(a: &apc);
232 TEST_ASSERT_EQ(atomic_load(&apc_reuse_destroyed), 1);
233 return TEST_SUCCESS;
234}
235
236static atomic_bool apc_race_ready = false;
237static atomic_bool apc_race_release = false;
238static atomic_bool apc_race_settled = false;
239static atomic_uint apc_race_ran = 0;
240static atomic_uint apc_race_destroyed = 0;
241
242static void raced_apc(void *arg) {
243 (void) arg;
244 atomic_fetch_add(&apc_race_ran, 1);
245}
246
247static void raced_apc_destroy(struct apc *apc) {
248 atomic_fetch_add(&apc_race_destroyed, 1);
249 kfree(apc);
250}
251
252static void apc_race_target(void *arg) {
253 (void) arg;
254 apc_disable_kernel();
255 atomic_store(&apc_race_ready, true);
256 while (!atomic_load(&apc_race_release))
257 scheduler_yield();
258 apc_enable_kernel();
259 while (!atomic_load(&apc_race_settled))
260 scheduler_yield();
261}
262
263TEST_DECLARE_INTEGRATION(apc, cancel_races_delivery) {
264 atomic_store(&apc_race_ready, false);
265 atomic_store(&apc_race_release, false);
266 atomic_store(&apc_race_settled, false);
267 atomic_store(&apc_race_ran, 0);
268 atomic_store(&apc_race_destroyed, 0);
269
270 struct thread *target =
271 thread_spawn_joinable(name: "apc_race_target", entry: apc_race_target, NULL);
272 TEST_ASSERT_NONNULL(target);
273 while (!atomic_load(&apc_race_ready))
274 scheduler_yield();
275
276 struct apc *apc = apc_create();
277 TEST_ASSERT_NONNULL(apc);
278 apc_init(a: apc, fn: raced_apc, NULL, destroy: raced_apc_destroy);
279 TEST_ASSERT(thread_get(target));
280 TEST_ASSERT(apc_enqueue(target, apc, APC_TYPE_KERNEL));
281
282 atomic_store(&apc_race_release, true);
283 bool cancelled = apc_cancel(t: target, a: apc);
284 thread_put(t: target);
285 atomic_store(&apc_race_settled, true);
286 apc_put(a: apc);
287 thread_join(t: target);
288
289 TEST_ASSERT_EQ(atomic_load(&apc_race_ran), (cancelled ? 0 : 1));
290 TEST_ASSERT_EQ(atomic_load(&apc_race_destroyed), 1);
291 return TEST_SUCCESS;
292}
293
294static atomic_uint the_event_apc_ran_times = 0;
295static atomic_bool event_apc_test_ok = false;
296static void the_event_apc(void *pc) {
297 atomic_fetch_add(&the_event_apc_ran_times, 1);
298}
299
300APC_EVENT_CREATE(apc_event_test, "TEST_EVENT");
301
302static void apc_event_test_thread(void *) {
303 /* We want to enqueue an event APC, then raise to DISPATCH, trigger it a
304 * few times, check that no APCs got triggered, and then lower from there,
305 * and then check that APCs got triggered, and then test masking, etc. */
306 struct event_apc *evtapc = apc_event_apc_create();
307 apc_event_apc_init(a: evtapc, fn: the_event_apc, NULL, destroy: apc_destroy_free);
308 TEST_ASSERT_VOID(apc_enqueue_event_apc(evtapc, APC_EVENT(apc_event_test)));
309 apc_put(a: &evtapc->apc);
310
311 enum irql old = irql_raise(new_level: IRQL_DISPATCH_LEVEL);
312 apc_event_signal(APC_EVENT(apc_event_test));
313 TEST_ASSERT_VOID_EQ(atomic_load(&the_event_apc_ran_times), 0);
314 irql_lower(old_level: old);
315
316 TEST_ASSERT_VOID_EQ(atomic_load(&the_event_apc_ran_times), 1);
317
318 apc_disable_kernel();
319 apc_event_signal(APC_EVENT(apc_event_test));
320 TEST_ASSERT_VOID_EQ(atomic_load(&the_event_apc_ran_times), 1);
321 apc_enable_kernel();
322
323 TEST_ASSERT_VOID_EQ(atomic_load(&the_event_apc_ran_times), 2);
324 apc_event_signal(APC_EVENT(apc_event_test));
325 TEST_ASSERT_VOID_EQ(atomic_load(&the_event_apc_ran_times), 3);
326 atomic_store(&event_apc_test_ok, true);
327}
328
329static struct thread *ated = NULL;
330TEST_DECLARE_INTEGRATION(apc, event_masking_and_signal) {
331 atomic_store(&the_event_apc_ran_times, 0);
332 atomic_store(&event_apc_test_ok, false);
333
334 ated = thread_spawn_joinable(name: "apc_event_test_thread", entry: apc_event_test_thread,
335 NULL);
336 TEST_ASSERT_NONNULL(ated);
337
338 /* joining rather than spinning on the ok flag means a failed
339 * TEST_ASSERT_VOID inside the thread reports instead of hanging */
340 thread_join(t: ated);
341 TEST_ASSERT(atomic_load(&event_apc_test_ok));
342
343 return TEST_SUCCESS;
344}
345