1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2022.
45//! Round Robin Scheduler for Tock
6//!
7//! This scheduler is specifically a Round Robin Scheduler with Interrupts.
8//!
9//! See: <https://www.eecs.umich.edu/courses/eecs461/lecture/SWArchitecture.pdf>
10//! for details.
11//!
12//! When hardware interrupts occur while a userspace process is executing, this
13//! scheduler executes the top half of the interrupt, and then stops executing
14//! the userspace process immediately and handles the bottom half of the
15//! interrupt. This design decision was made to mimic the behavior of the
16//! original Tock scheduler. In order to ensure fair use of timeslices, when
17//! userspace processes are interrupted the scheduler timer is paused, and the
18//! same process is resumed with the same scheduler timer value from when it was
19//! interrupted.
2021use core::cell::Cell;
22use core::num::NonZeroU32;
2324use crate::collections::list::{List, ListLink, ListNode};
25use crate::platform::chip::Chip;
26use crate::process::Process;
27use crate::process::StoppedExecutingReason;
28use crate::scheduler::{Scheduler, SchedulingDecision};
2930/// A node in the linked list the scheduler uses to track processes
31/// Each node holds a pointer to a slot in the processes array
32pub struct RoundRobinProcessNode<'a> {
33 proc: &'static Option<&'static dyn Process>,
34 next: ListLink<'a, RoundRobinProcessNode<'a>>,
35}
3637impl<'a> RoundRobinProcessNode<'a> {
38pub fn new(proc: &'static Option<&'static dyn Process>) -> RoundRobinProcessNode<'a> {
39 RoundRobinProcessNode {
40 proc,
41 next: ListLink::empty(),
42 }
43 }
44}
4546impl<'a> ListNode<'a, RoundRobinProcessNode<'a>> for RoundRobinProcessNode<'a> {
47fn next(&'a self) -> &'a ListLink<'a, RoundRobinProcessNode<'a>> {
48&self.next
49 }
50}
5152/// Round Robin Scheduler
53pub struct RoundRobinSched<'a> {
54 time_remaining: Cell<u32>,
55 timeslice_length: u32,
56pub processes: List<'a, RoundRobinProcessNode<'a>>,
57 last_rescheduled: Cell<bool>,
58}
5960impl<'a> RoundRobinSched<'a> {
61/// How long a process can run before being pre-empted
62const DEFAULT_TIMESLICE_US: u32 = 10000;
63pub const fn new() -> RoundRobinSched<'a> {
64Self::new_with_time(Self::DEFAULT_TIMESLICE_US)
65 }
6667pub const fn new_with_time(time_us: u32) -> RoundRobinSched<'a> {
68 RoundRobinSched {
69 time_remaining: Cell::new(time_us),
70 timeslice_length: time_us,
71 processes: List::new(),
72 last_rescheduled: Cell::new(false),
73 }
74 }
75}
7677impl<C: Chip> Scheduler<C> for RoundRobinSched<'_> {
78fn next(&self) -> SchedulingDecision {
79let mut first_head = None;
80let mut next = None;
8182// Find the first ready process in the queue. Place any *empty* process slots,
83 // or not-ready processes, at the back of the queue.
84while let Some(node) = self.processes.head() {
85// Ensure we do not loop forever if all processes are not ready
86match first_head {
87None => first_head = Some(node),
88Some(first_head) => {
89// We made a full iteration and nothing was ready. Try to sleep instead
90if core::ptr::eq(first_head, node) {
91return SchedulingDecision::TrySleep;
92 }
93 }
94 }
95match node.proc {
96Some(proc) => {
97if proc.ready() {
98 next = Some(proc.processid());
99break;
100 }
101self.processes.push_tail(self.processes.pop_head().unwrap());
102 }
103None => {
104self.processes.push_tail(self.processes.pop_head().unwrap());
105 }
106 }
107 }
108109let next = match next {
110Some(p) => p,
111None => {
112// No processes on the system
113return SchedulingDecision::TrySleep;
114 }
115 };
116117let timeslice = if self.last_rescheduled.get() {
118self.time_remaining.get()
119 } else {
120// grant a fresh timeslice
121self.time_remaining.set(self.timeslice_length);
122self.timeslice_length
123 };
124// Why should this panic?
125let non_zero_timeslice = NonZeroU32::new(timeslice).unwrap();
126127 SchedulingDecision::RunProcess((next, Some(non_zero_timeslice)))
128 }
129130fn result(&self, result: StoppedExecutingReason, execution_time_us: Option<u32>) {
131let execution_time_us = execution_time_us.unwrap(); // should never fail
132let reschedule = match result {
133 StoppedExecutingReason::KernelPreemption => {
134if self.time_remaining.get() > execution_time_us {
135self.time_remaining
136 .set(self.time_remaining.get() - execution_time_us);
137true
138} else {
139false
140}
141 }
142_ => false,
143 };
144self.last_rescheduled.set(reschedule);
145if !reschedule {
146self.processes.push_tail(self.processes.pop_head().unwrap());
147 }
148 }
149}