1// SPDX-License-Identifier: GPL-3.0-or-later
2
3#include "mos/tasks/schedule.hpp"
4
5#include "mos/assert.hpp"
6#include "mos/lib/sync/spinlock.hpp"
7#include "mos/misc/setup.hpp"
8#include "mos/platform/platform.hpp"
9#include "mos/tasks/scheduler.hpp"
10
11#include <mos_string.hpp>
12
13char thread_state_str(thread_state_t state)
14{
15 switch (state)
16 {
17 case THREAD_STATE_CREATED: return 'C';
18 case THREAD_STATE_READY: return 'R';
19 case THREAD_STATE_RUNNING: return 'r';
20 case THREAD_STATE_BLOCKED: return 'B';
21 case THREAD_STATE_NONINTERRUPTIBLE: return 'N';
22 case THREAD_STATE_DEAD: return 'D';
23 }
24
25 MOS_UNREACHABLE();
26}
27
28static bool scheduler_ready = false;
29static scheduler_t *active_scheduler = NULL;
30extern const scheduler_info_t __MOS_SCHEDULERS_START[], __MOS_SCHEDULERS_END[];
31
32MOS_SETUP("scheduler", scheduler_cmdline_selector)
33{
34 for (const scheduler_info_t *info = __MOS_SCHEDULERS_START; info < __MOS_SCHEDULERS_END; info++)
35 {
36 if (info->name == arg)
37 {
38 active_scheduler = info->scheduler;
39 active_scheduler->ops->init(active_scheduler);
40 pr_dinfo2(scheduler, "active scheduler: %s", info->name);
41 return true;
42 }
43 }
44
45 pr_dwarn(scheduler, "scheduler '%s' not found", arg);
46 return false;
47}
48
49void scheduler_init()
50{
51 if (!active_scheduler)
52 {
53 pr_dwarn(scheduler, "no scheduler is selected, using the first scheduler");
54 active_scheduler = __MOS_SCHEDULERS_START[0].scheduler;
55 active_scheduler->ops->init(active_scheduler);
56 }
57}
58
59void unblock_scheduler(void)
60{
61 pr_dinfo2(scheduler, "unblocking scheduler");
62 MOS_ASSERT_X(!scheduler_ready, "scheduler is already unblocked");
63 scheduler_ready = true;
64}
65
66[[noreturn]] void enter_scheduler(void)
67{
68 while (likely(!scheduler_ready))
69 ; // wait for the scheduler to be unblocked
70
71 pr_dinfo2(scheduler, "cpu %d: scheduler is ready", platform_current_cpu_id());
72 MOS_ASSERT(current_thread == nullptr);
73 reschedule();
74 MOS_UNREACHABLE();
75}
76
77void scheduler_add_thread(Thread *thread)
78{
79 MOS_ASSERT(Thread::IsValid(thread));
80 MOS_ASSERT_X(thread->state == THREAD_STATE_CREATED || thread->state == THREAD_STATE_READY, "thread %pt is not in a valid state", thread);
81 active_scheduler->ops->add_thread(active_scheduler, thread);
82}
83
84void scheduler_remove_thread(Thread *thread)
85{
86 MOS_ASSERT(Thread::IsValid(thread));
87 active_scheduler->ops->remove_thread(active_scheduler, thread);
88}
89
90void scheduler_wake_thread(Thread *thread)
91{
92 spinlock_acquire(&thread->state_lock);
93 if (thread->state == THREAD_STATE_READY || thread->state == THREAD_STATE_RUNNING || thread->state == THREAD_STATE_CREATED || thread->state == THREAD_STATE_DEAD)
94 {
95 spinlock_release(&thread->state_lock);
96 return; // thread is already running or ready
97 }
98
99 MOS_ASSERT_X(thread->state == THREAD_STATE_BLOCKED || thread->state == THREAD_STATE_NONINTERRUPTIBLE, "thread %pt is not blocked", thread);
100 thread->state = THREAD_STATE_READY;
101 spinlock_release(&thread->state_lock);
102 pr_dinfo2(scheduler, "waking up %pt", thread);
103 active_scheduler->ops->add_thread(active_scheduler, thread);
104}
105
106void reschedule(void)
107{
108 // A thread can jump to the scheduler if it is:
109 // - in RUNNING state normal condition (context switch caused by timer interrupt or yield())
110 // - in CREATED state the thread is not yet started
111 // - in DEAD state the thread is exiting, and the scheduler will clean it up
112 // - in BLOCKED state the thread is waiting for a condition, and we'll schedule to other threads
113 // But it can't be:
114 // - in READY state
115 cpu_t *cpu = current_cpu;
116
117 auto next = active_scheduler->ops->select_next(active_scheduler);
118
119 if (!next)
120 {
121 if (current_thread && current_thread->state == THREAD_STATE_RUNNING)
122 {
123 // give the current thread another chance to run, if it's the only one and it's able to run
124 MOS_ASSERT_X(spinlock_is_locked(&current_thread->state_lock), "thread state lock must be held");
125 pr_dinfo2(scheduler, "no thread to run, staying with %pt, state = %c", current_thread, thread_state_str(current_thread->state));
126 spinlock_release(&current_thread->state_lock);
127 return;
128 }
129
130 next = cpu->idle_thread;
131 }
132
133 const bool should_switch_mm = cpu->mm_context != next->owner->mm;
134 if (should_switch_mm)
135 {
136 MMContext *old = mm_switch_context(new_ctx: next->owner->mm);
137 MOS_UNUSED(old);
138 }
139
140 const ContextSwitchBehaviorFlags switch_flags = statement_expr(ContextSwitchBehaviorFlags, {
141 retval = SWITCH_REGULAR;
142 if (next->state == THREAD_STATE_CREATED)
143 retval |= next->mode == THREAD_MODE_KERNEL ? SWITCH_TO_NEW_KERNEL_THREAD : SWITCH_TO_NEW_USER_THREAD;
144 });
145
146 if (likely(current_thread))
147 {
148 if (current_thread->state == THREAD_STATE_RUNNING)
149 {
150 current_thread->state = THREAD_STATE_READY;
151 if (current_thread != cpu->idle_thread)
152 scheduler_add_thread(current_thread);
153 }
154 pr_dinfo2(scheduler, "leaving %pt, state: '%c'", current_thread, thread_state_str(current_thread->state));
155 }
156 pr_dinfo2(scheduler, "switching to %pt, state: '%c'", next, thread_state_str(next->state));
157
158 next->state = THREAD_STATE_RUNNING;
159 spinlock_release(&next->state_lock);
160 platform_switch_to_thread(current_thread, new_thread: next, switch_flags);
161}
162
163void blocked_reschedule(void)
164{
165 spinlock_acquire(&current_thread->state_lock);
166 current_thread->state = THREAD_STATE_BLOCKED;
167 pr_dinfo2(scheduler, "%pt is now blocked", current_thread);
168 reschedule();
169}
170
171bool reschedule_for_waitlist(waitlist_t *waitlist)
172{
173 MOS_ASSERT_X(current_thread->state != THREAD_STATE_BLOCKED, "thread %d is already blocked", current_thread->tid);
174
175 if (!waitlist_append(list: waitlist))
176 return false; // waitlist is closed, process is dead
177
178 blocked_reschedule();
179 return true;
180}
181