1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
//! The Completly Unfair Scheduler

use alloc::sync::Arc;
use alloc::vec::Vec;
use core::mem;

use crate::process::{ProcessStruct, ThreadStruct, ThreadState};
use crate::i386::process_switch::process_switch;
use crate::sync::{Lock, SpinLockIRQ, SpinLockIRQGuard};
use core::sync::atomic::Ordering;
use crate::error::{UserspaceError};
use sunrise_libkern::TLS;
use core::cell::RefCell;
use crate::cpu_locals::ARE_CPU_LOCALS_INITIALIZED_YET;

/// An Arc to the currently running thread.
///
/// In the early kernel initialization, this will be None. Once the first thread takes
/// over, this variable is guaranteed to always be Some and never go back to None - if
/// all threads are currently waiting, the global will point to whatever the last
/// thread was running. A useful side-effect: the CURRENT_PROCESS's pmemory will *always*
/// be the current CR3.
///
/// A side-effect of this guarantee is, if the last thread dies, it will still be kept alive
/// through this global!
///
/// # Safety
///
/// Setting this value should be done through set_current_thread, otherwise Bad Things:tm:
/// will happen.
#[thread_local] // this is a cpu_local
static CURRENT_THREAD: RefCell<Option<Arc<ThreadStruct>>> = RefCell::new(None);

/// Gets the current ThreadStruct, incrementing its refcount.
/// Will return None if we're in an early boot state, and it has not yet been initialized.
pub fn try_get_current_thread() -> Option<Arc<ThreadStruct>> {
    // if cpu_locals haven't been initialized, accessing gs:0 will triple fault,
    // so don't even remotely try to access it.
    if !ARE_CPU_LOCALS_INITIALIZED_YET.load(Ordering::Relaxed) {
        None
    } else {
        CURRENT_THREAD.borrow().clone()
    }
}

/// Gets the current ThreadStruct, incrementing its refcount.
pub fn get_current_thread() -> Arc<ThreadStruct> {
    try_get_current_thread().unwrap()
}

/// Gets the ProcessStruct of the current thread, incrementing its refcount.
/// Will return None if we're in an early boot state, and it has not yet been initialized.
pub fn try_get_current_process() -> Option<Arc<ProcessStruct>> {
    try_get_current_thread().map(|t| t.process.clone())
}

/// Gets the ProcessStruct of the current thread, incrementing its refcount.
pub fn get_current_process() -> Arc<ProcessStruct> {
    try_get_current_process().unwrap()
}

/// Sets the current ThreadStruct.
///
/// Note that if `CURRENT_THREAD` was the last reference to the current thread, this is where it will
/// be dropped.
///
/// Setting the current thread should *always* go through this function, and never
/// by setting [`CURRENT_THREAD`] directly. This function uses mem::replace to ensure
/// that the ThreadStruct's Drop is run with CURRENT_THREAD set to the *new* value.
///
/// The passed function will be executed after setting the CURRENT_THREAD, but before
/// setting it back to the RUNNING state.
///
/// # Unsafety
///
/// Interrupts must be disabled when calling this function. It will mutably borrow [`CURRENT_THREAD`],
/// so we can't have interrupts on top of that which try to access it while it is borrowed mutably by us,
/// otherwise the kernel will panic.
#[allow(clippy::needless_pass_by_value)] // more readable
unsafe fn set_current_thread<R, F: FnOnce() -> R>(t: Arc<ThreadStruct>, f: F) -> R {
    let old_thread = {
        mem::replace(&mut *CURRENT_THREAD.borrow_mut(), Some(t.clone()))
    };
    // drop RefMut first, then old thread.
    drop(old_thread);

    let r = f();

    let _ = t.state.compare_exchange(ThreadState::Scheduled, ThreadState::Running, Ordering::SeqCst, Ordering::SeqCst);

    r
}

/// The schedule queue
///
/// It's a simple vec, acting as a round-robin, first element is the running thread.
/// When its time slice has ended, it is rotated to the end of the vec, and we go on to the next one.
///
/// The vec is protected by a SpinLockIRQ, so accessing/modifying it disables irqs.
/// Since there's no SMP, this should guarantee we cannot deadlock in the scheduler.
static SCHEDULE_QUEUE: SpinLockIRQ<Vec<Arc<ThreadStruct>>> = SpinLockIRQ::new(Vec::new());

/// Adds a thread at the end of the schedule queue, and changes its state to 'scheduled'
/// Thread must be ready to be scheduled.
///
/// If the thread was already scheduled, this function is a Noop.
///
/// # Panics
///
/// Panics if the thread's state was already "Scheduled"
pub fn add_to_schedule_queue(thread: Arc<ThreadStruct>) {

    let mut queue_lock = SCHEDULE_QUEUE.lock();

    if is_in_schedule_queue(&queue_lock, &thread) {
        return;
    }

    let oldstate = thread.state.compare_exchange(ThreadState::Paused, ThreadState::Scheduled, Ordering::SeqCst, Ordering::SeqCst);
    let oldstate = match oldstate {
        Ok(v) => v,
        Err(v) => v
    };

    assert!(oldstate == ThreadState::Paused || oldstate == ThreadState::TerminationPending,
               "Process added to schedule queue was not stopped : {:?}", oldstate);

    queue_lock.push(thread)
}

/// Checks if a thread is already either in the schedule queue or currently running.
pub fn is_in_schedule_queue(queue: &SpinLockIRQGuard<'_, Vec<Arc<ThreadStruct>>>,
                            thread: &Arc<ThreadStruct>) -> bool {
    CURRENT_THREAD.borrow().iter().filter(|v| {
        v.state.load(Ordering::SeqCst) != ThreadState::Paused
    }).chain(queue.iter()).any(|elem| Arc::ptr_eq(thread, elem))
}

/// Removes the current thread from the schedule queue, and schedule.
///
/// The passed lock will remain locked until the thread is safely removed from the schedule queue.
/// In other words, event handling code should wait for that lock to be dropped before attempting
/// to call `add_to_schedule_queue`.
///
/// The reason behind this behavior is that `add_to_schedule_queue` checks if a thread is currently
/// in the schedule queue, before adding it in. It does the check by checking if the thread is
/// either in the list of threads to run, or if it's the currently running one and in a Running
/// state. The lock will be dropped once the thread is transitioned to the Stopped state, allowing
/// `add_to_schedule_queue` to work again.
///
/// It will be relocked just before the thread starts running again. Specifically, it will be
/// relocked when CURRENT_THREAD is set back to the current thread, but before its state is
/// changed back to Running. This allows using SpinLockIRQs as a lock.
///
/// The lock should be used to avoid race conditions between registering for an event, and unscheduling.
///
/// The current thread will not be ran again unless it was registered for rescheduling.
pub fn unschedule<'a, LOCK, GUARD>(lock: &'a LOCK, guard: GUARD) -> Result<GUARD, UserspaceError>
where
    LOCK: Lock<'a, GUARD>,
    GUARD: 'a
{
    {
        let thread = get_current_thread();
        let old = thread.state.compare_exchange(ThreadState::Running, ThreadState::Paused, Ordering::SeqCst, Ordering::SeqCst);
        let old = match old {
            Ok(v) => v,
            Err(v) => v
        };
        assert!(old == ThreadState::TerminationPending || old == ThreadState::Running, "Old was in invalid state {:?} before unscheduling", old);
        mem::drop(guard)
    }

    let guard = internal_schedule(lock, true);

    if get_current_thread().state.load(Ordering::SeqCst) == ThreadState::TerminationPending {
        Err(UserspaceError::Canceled)
    } else {
        Ok(guard)
    }
}

/// Creates the very first process at boot.
/// The created process has 1 thread, which is marked as the current thread, and added to the schedule queue.
///
/// # Safety
///
/// Use only for creating the very first process. Should never be used again after that.
/// Must be using a valid KernelStack, a valid ActivePageTables.
///
/// # Panics
///
/// Panics if the schedule queue was not empty
pub unsafe fn create_first_process() {
    let queue = SCHEDULE_QUEUE.lock();
    assert!(queue.is_empty());
    let thread_0 = ThreadStruct::create_first_thread();
    unsafe {
        // provided we only run this function once, it hasn't been initialized yet
        set_current_thread(thread_0, || ());
    }
}

/// Performs a process switch.
///
/// # Queue politics
///
/// ```txt
///                           checking if thread is unlocked
///                           and suitable for running
///   CURRENT_THREAD          ===============================>
///     j--------j          j--------j j--------j j--------j
///     | current|          |    X   | |        | |        |
///     j--------j          j--------j j--------j j--------j    A
///        | A               locked,       |                    |
///        | |               skipped       |                    |
///        | +-----------------------------+                    |
///        +----------------------------------------------------+
/// ```
///
/// 1. Tries to lock the next first process. If it fails to acquire its lock,
///    it is ignored for now, and we move on to the next one.
/// 2. When a candidate is found, it is removed from the queue, and
///    set as CURRENT_THREAD.
/// 3. Pushes the previous current thread at the end of the queue.
/// 4. Disables interrupts
/// 5. Performs the process switch
/// 6. * as new process * Re-enables interrupts
pub fn schedule() {
    /// A dummy Lock.
    struct NoopLock;
    impl Lock<'static, ()> for NoopLock {
        fn lock(&self) { /* no-op */ }
    }

    internal_schedule(&NoopLock, false);
}

/// Parses the queue to find the first unlocked process.
/// Returns the index of found process
fn find_next_thread_to_run(queue: &[Arc<ThreadStruct>]) -> Option<usize> {
    for (index, thread) in queue.iter().enumerate() {
        if thread.hwcontext.try_lock().is_some() {
            return Some(index)
        }
    }
    None
}

/// Internal impl of the process switch, used by schedule and unschedule.
///
/// See schedule function for documentation on how scheduling works.
fn internal_schedule<'a, LOCK, GUARD>(lock: &'a LOCK, remove_self: bool) -> GUARD
where
    LOCK: Lock<'a, GUARD>,
    GUARD: 'a
{
    // TODO: Ensure the global counter is <= 1

    let interrupt_manager = SpinLockIRQ::new(());
    let mut interrupt_lock = interrupt_manager.lock();

    loop {
        let mut queue = SCHEDULE_QUEUE.lock();

        let candidate_index = find_next_thread_to_run(&queue);
        let retguard = match (candidate_index, remove_self) {
            (None, true) => {
                // There's nobody to schedule. Let's drop all the locks, HLT, and run internal_schedule again.
                // NOTE: There's nobody running at this point. :O
                drop(queue);
                // Temporarily revive interrupts for hlt.
                drop(interrupt_lock);
                unsafe {
                    crate::i386::instructions::interrupts::hlt();
                }

                // Kill interrupts again.
                interrupt_lock = interrupt_manager.lock();

                // Rerun internal_schedule.
                continue;
            },
            (None, false) => {
                // There's nobody else to run. Let's keep running ourselves...
                drop(queue);
                lock.lock()
            }
            (Some(index_b), _) => {
                // 1. remove canditate from the queue, pushing remaining of the queue to the front
                let process_b = queue.remove(index_b);

                // 2. push current at the back of the queue, unless we want to unschedule it.
                let proc = get_current_thread();
                if !remove_self {
                    queue.push(proc.clone());
                }

                // unlock the queue
                drop(queue);

                let whoami = if !Arc::ptr_eq(&process_b, &proc) {
                    unsafe {
                        // safety: interrupts are disabled by the interrupt_lock.
                        process_switch(process_b, proc)
                    }
                } else {
                    // Avoid process switching if we're just rescheduling ourselves.
                    proc
                };

                /* We were scheduled again. To prevent race conditions, relock the lock now. */

                // replace CURRENT_THREAD with ourself.
                // If previously running thread had deleted all other references to itself, this
                // is where its drop actually happens
                unsafe {
                    // safety: interrupts are disabled by the interrupt_lock.
                    set_current_thread(whoami, || lock.lock())
                }
            }
        };
        break retguard;
    }
}


/// The function called when a thread was scheduled for the first time,
/// right after the arch-specific process switch was performed.
///
/// It takes a reference to the current thread (which will be set), and a function that should jump
/// to this thread's entrypoint.
///
/// The passed function should take care to change the protection level, and ensure it cleans up all
/// the registers before calling the EIP, in order to avoid leaking information to userspace.
///
/// # Safety
///
/// Interrupts must be off when calling this function. It will set [`CURRENT_THREAD`], and then
/// turn them on, as we are running a new thread, no SpinLockIRQ is held.
pub unsafe fn scheduler_first_schedule<F: FnOnce()>(current_thread: Arc<ThreadStruct>, jump_to_entrypoint: F) {
    // replace CURRENT_THREAD with ourself.
    // If previously running thread had deleted all other references to itself, this
    // is where its drop actually happens
    unsafe {
        // safety: interrupts are off
        set_current_thread(current_thread, || ())
    };

    unsafe {
        // this is a new process, no SpinLockIRQ is held
        crate::i386::instructions::interrupts::sti();
    }

    // memset the TLS, to clear previous owner's data.
    // we do it here so don't have to CrossProcessMap it earlier.
    unsafe {
        // safe: we manage this memory, ptr is aligned, 0 is valid for every field of the TLS,
        //       and TLS contains no padding bytes.
        let tls_ptr = get_current_thread().tls_region.addr() as *mut TLS;
        core::ptr::write_bytes(tls_ptr, 0u8, 1);
        (*tls_ptr).ptr_self = tls_ptr
    }

    jump_to_entrypoint()
}