| 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 | |
| 14 | static atomic_bool workqueue_ran = false; |
| 15 | static _Atomic uint32_t workqueue_times = 0; |
| 16 | static 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 | |
| 22 | TEST_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 | |
| 55 | static void sleepy_entry(void *) { |
| 56 | thread_sleep_for_ms(ms: 9000); |
| 57 | thread_print(t: thread_get_current()); |
| 58 | } |
| 59 | |
| 60 | TEST_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 | |
| 68 | static _Atomic uint32_t times_2 = 0; |
| 69 | |
| 70 | static void wq_test_2(void *a, void *b) { |
| 71 | (void) a, (void) b; |
| 72 | atomic_fetch_add(×_2, 1); |
| 73 | for (uint64_t i = 0; i < 500; i++) |
| 74 | cpu_relax(); |
| 75 | } |
| 76 | |
| 77 | static struct workqueue *wq = NULL; |
| 78 | static _Atomic uint32_t threads_left = WQ_2_THREADS; |
| 79 | |
| 80 | static 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 | |
| 91 | TEST_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 | |
| 132 | static atomic_bool daemon_work_run = false; |
| 133 | static 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 | |
| 138 | static struct daemon_work dwork = |
| 139 | DAEMON_WORK_FROM(daemon_work, WORK_ARGS(NULL, NULL)); |
| 140 | |
| 141 | TEST_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 | |
| 166 | static atomic_bool si_apc_ran = false; |
| 167 | static struct thread *si_t; |
| 168 | static atomic_bool si_ok = false; |
| 169 | static atomic_bool si_started = false; |
| 170 | |
| 171 | static void apc_si(void *apc) { |
| 172 | atomic_store(&si_apc_ran, true); |
| 173 | } |
| 174 | |
| 175 | static 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 | |
| 185 | static 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 | |
| 196 | static 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 | |
| 204 | TEST_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 | |
| 219 | static atomic_bool gogo = false; |
| 220 | static atomic_bool eq = false; |
| 221 | |
| 222 | static 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 | |
| 229 | static 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 */ |
| 243 | TEST_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 | |
| 275 | static atomic_uint left = SCHED_PUSH_TEST_THREADS; |
| 276 | static atomic_bool at_least_one_migrated = false; |
| 277 | |
| 278 | static 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 | |
| 286 | TEST_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 | |