1#include "internal.h"
2#include <thread/apc.h>
3
4/* file implements `thread_migrate`.
5 *
6 * NOTE: when we acquire two `struct scheduler` locks,
7 * we ALWAYS ALWAYS acquire the lock for the LOWER
8 * memory address first, same for unlock,
9 * and we ALWAYS acquire thread
10 * locks AFTER any scheduler locks! */
11
12/*
13 * # Small Idea: Thread being_moved locking
14 *
15 * ## Credits: gummi
16 *
17 * ## Context: This flag is used to ensure that everyone reads an accurate
18 * `last_ran` field on a thread. Basically, READY threads can be
19 * arbitrarily migrated at any time if it is possible. All
20 * migrations must occur whilst both scheduler locks are held.
21 *
22 * Threads also have to have their core's scheduler lock
23 * acquired when the being_moved is being locked.
24 *
25 * ## Problem: We need to guarantee that this lock is used appropriately
26 * alongside thread flags to make sure that an invalid
27 * last_ran field is NEVER read. This must be done
28 * because if an invalid last_ran field is read, we will
29 * lock the WRONG scheduler and can cause catastrophic races.
30 *
31 * ## Strategy: Upon any thread migration within the scheduler, we spin
32 * on the lock and try to set it. Upon any read of the
33 * thread's last_ran field, we spin on the lock and wait
34 * for it to become free but never set it. Prior to any
35 * spin_raw on the lock from outside the scheduler,
36 * we ALWAYS set the thread as NO_STEAL so that it cannot
37 * possibly be migrated after the lock is available.
38 *
39 * Every time we read the flags to see if the thread can
40 * be migrated, we must hold the lock prior to reading the
41 * flags, and release the lock if we are able to migrate
42 * the thread and only after we set last_ran.
43 *
44 * ## Changelog:
45 * 12/4/2025 - gummi - Created Idea
46 *
47 *
48 */
49
50void thread_post_migrate(struct thread *t, size_t old_cpu, size_t new_cpu) {
51 /* assert that both scheduler/runqueue locks are held,
52 * migrate relevant things */
53 SPINLOCK_ASSERT_HELD(&global.schedulers[old_cpu]->lock);
54 SPINLOCK_ASSERT_HELD(&global.schedulers[new_cpu]->lock);
55 climb_post_migrate_hook(t, old_cpu, new_cpu);
56}
57
58void thread_migrate(struct thread *t, size_t dest_core) {
59 /* first acquire both the lock of the thread's scheduler
60 * and the destination core's scheduler */
61
62 enum irql sirql, dirql, tirql;
63
64 struct scheduler *src;
65 struct scheduler *dst = global.schedulers[dest_core];
66
67 thread_lock_thread_and_rq(t, other_rq: dst, out_thread_rq: &src, irq_first: &sirql, irq_second: &dirql);
68
69 if (src == dst) {
70 thread_unlock_thread_and_rq(thread_rq: src, other_rq: dst, irq_first: sirql, irq_second: dirql);
71 return;
72 }
73
74 /* now that we have acquired both scheduler locks,
75 * we have control over the thread and can
76 * unmark it as NO_STEAL */
77
78 bool ok;
79 tirql = thread_acquire(t, success: &ok);
80 if (!ok) {
81 goto end;
82 }
83
84 /* bro cannot be migrated */
85 if (!scheduler_can_take_thread(core: dest_core, target: t)) {
86 goto out;
87 }
88
89 /* finally, we can do something here. */
90 if (thread_get_state(t) == THREAD_STATE_RUNNING ||
91 !(thread_get_flags(t) & THREAD_FLAG_YIELDED)) {
92 thread_set_migration_target(t, new: dest_core);
93 scheduler_force_resched(sched: dst);
94 } else if (thread_get_state(t) == THREAD_STATE_READY) {
95 scheduler_remove_thread(sched: src, t, /* lock_held = */ true);
96 scheduler_add_thread(sched: dst, thread: t, /* lock_held = */ true);
97 }
98
99 thread_set_runqueue(t, s: dst);
100
101out:
102
103 thread_release(t, irql: tirql);
104
105end:
106 thread_unlock_thread_and_rq(thread_rq: src, other_rq: dst, irq_first: sirql, irq_second: dirql);
107}
108