218 lines
6.0 KiB
Rust
218 lines
6.0 KiB
Rust
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
use alloc::collections::vec_deque::VecDeque;
|
|
use core::{
|
|
array,
|
|
num::NonZero,
|
|
sync::atomic::{AtomicU8, Ordering::*},
|
|
};
|
|
|
|
use bitvec::{bitarr, BitArr};
|
|
|
|
use super::{time::base_slice_clocks, *};
|
|
|
|
pub type RtPrio = RangedU8<1, 99>;
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
|
pub enum RealTimePolicy {
|
|
Fifo,
|
|
RoundRobin {
|
|
base_slice_factor: Option<NonZero<u32>>,
|
|
},
|
|
}
|
|
|
|
impl Default for RealTimePolicy {
|
|
fn default() -> Self {
|
|
Self::RoundRobin {
|
|
base_slice_factor: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl RealTimePolicy {
|
|
fn to_time_slice(self) -> u64 {
|
|
match self {
|
|
RealTimePolicy::RoundRobin { base_slice_factor } => {
|
|
base_slice_clocks()
|
|
* base_slice_factor
|
|
.map_or(DEFAULT_BASE_SLICE_FACTOR, |factor| u64::from(factor.get()))
|
|
}
|
|
RealTimePolicy::Fifo => 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The scheduling attribute for the REAL-TIME scheduling class.
|
|
///
|
|
/// This structure provides not-only the priority of the thread,
|
|
/// but also the time slice for the thread, measured in [`sched_clock`]s.
|
|
///
|
|
/// - If the time slice is not set, the thread is considered to be a FIFO
|
|
/// thread, and will be executed to its end if there no thread with a
|
|
/// lower priority.
|
|
/// - If the time slice is set, the thread is considered to be an RR
|
|
/// (round-robin) thread, and will be executed for the time slice, and
|
|
/// then it will be put back to the inactive array.
|
|
#[derive(Debug)]
|
|
pub struct RealTimeAttr {
|
|
prio: AtomicU8,
|
|
time_slice: AtomicU64, // 0 for SCHED_FIFO; other for SCHED_RR
|
|
}
|
|
|
|
const DEFAULT_BASE_SLICE_FACTOR: u64 = 20;
|
|
|
|
impl RealTimeAttr {
|
|
pub fn new(prio: u8, policy: RealTimePolicy) -> Self {
|
|
RealTimeAttr {
|
|
prio: prio.into(),
|
|
time_slice: AtomicU64::new(policy.to_time_slice()),
|
|
}
|
|
}
|
|
|
|
pub fn update(&self, prio: u8, policy: RealTimePolicy) {
|
|
self.prio.store(prio, Relaxed);
|
|
self.time_slice.store(policy.to_time_slice(), Relaxed);
|
|
}
|
|
}
|
|
|
|
struct PrioArray {
|
|
map: BitArr![for 100],
|
|
queue: [VecDeque<Arc<Task>>; 100],
|
|
}
|
|
|
|
impl core::fmt::Debug for PrioArray {
|
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
f.debug_struct("PrioArray")
|
|
.field_with("map", |f| {
|
|
f.debug_list().entries(self.map.iter_ones()).finish()
|
|
})
|
|
.field_with("queue", |f| {
|
|
f.debug_list()
|
|
.entries(
|
|
(self.queue.iter().flatten())
|
|
.map(|task| task.as_thread().unwrap().sched_attr()),
|
|
)
|
|
.finish()
|
|
})
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
impl PrioArray {
|
|
fn enqueue(&mut self, thread: Arc<Task>, prio: u8) {
|
|
let queue = &mut self.queue[usize::from(prio)];
|
|
let is_empty = queue.is_empty();
|
|
queue.push_back(thread);
|
|
if is_empty {
|
|
self.map.set(usize::from(prio), true);
|
|
}
|
|
}
|
|
|
|
fn pop(&mut self) -> Option<Arc<Task>> {
|
|
let mut iter = self.map.iter_ones();
|
|
let prio = iter.next()? as u8;
|
|
|
|
let queue = &mut self.queue[usize::from(prio)];
|
|
let thread = queue.pop_front()?;
|
|
|
|
if queue.is_empty() {
|
|
self.map.set(usize::from(prio), false);
|
|
}
|
|
Some(thread)
|
|
}
|
|
}
|
|
|
|
/// The per-cpu run queue for the REAL-TIME scheduling class.
|
|
///
|
|
/// The REAL-TIME scheduling class is implemented as a classic O(1)
|
|
/// priority algorithm.
|
|
///
|
|
/// It uses a bit array to track which priority levels have runnable
|
|
/// threads, and a vector of queues to store the threads.
|
|
///
|
|
/// Threads are popped & dequeued from the active array (`array[index]`), and
|
|
/// are enqueued into the inactive array (`array[!index]`). When the active array
|
|
/// is empty, the 2 arrays are swapped by `index`.
|
|
#[derive(Debug)]
|
|
pub(super) struct RealTimeClassRq {
|
|
#[allow(unused)]
|
|
cpu: CpuId,
|
|
index: bool,
|
|
array: [PrioArray; 2],
|
|
nr_running: usize,
|
|
}
|
|
|
|
impl RealTimeClassRq {
|
|
pub fn new(cpu: CpuId) -> RealTimeClassRq {
|
|
RealTimeClassRq {
|
|
cpu,
|
|
index: false,
|
|
array: array::from_fn(|_| PrioArray {
|
|
map: bitarr![0; 100],
|
|
queue: array::from_fn(|_| VecDeque::new()),
|
|
}),
|
|
nr_running: 0,
|
|
}
|
|
}
|
|
|
|
fn active_array(&mut self) -> &mut PrioArray {
|
|
&mut self.array[usize::from(self.index)]
|
|
}
|
|
|
|
fn inactive_array(&mut self) -> &mut PrioArray {
|
|
&mut self.array[usize::from(!self.index)]
|
|
}
|
|
|
|
fn swap_arrays(&mut self) {
|
|
self.index = !self.index;
|
|
}
|
|
}
|
|
|
|
impl SchedClassRq for RealTimeClassRq {
|
|
fn enqueue(&mut self, entity: Arc<Task>, _: Option<EnqueueFlags>) {
|
|
let sched_attr = entity.as_thread().unwrap().sched_attr();
|
|
let prio = sched_attr.real_time.prio.load(Relaxed);
|
|
self.inactive_array().enqueue(entity, prio);
|
|
self.nr_running += 1;
|
|
}
|
|
|
|
fn len(&mut self) -> usize {
|
|
self.nr_running
|
|
}
|
|
|
|
fn is_empty(&mut self) -> bool {
|
|
self.nr_running == 0
|
|
}
|
|
|
|
fn pick_next(&mut self) -> Option<Arc<Task>> {
|
|
if self.nr_running == 0 {
|
|
return None;
|
|
}
|
|
|
|
(self.active_array().pop())
|
|
.or_else(|| {
|
|
self.swap_arrays();
|
|
self.active_array().pop()
|
|
})
|
|
.inspect(|_| self.nr_running -= 1)
|
|
}
|
|
|
|
fn update_current(
|
|
&mut self,
|
|
rt: &CurrentRuntime,
|
|
attr: &SchedAttr,
|
|
flags: UpdateFlags,
|
|
) -> bool {
|
|
let attr = &attr.real_time;
|
|
|
|
match flags {
|
|
UpdateFlags::Tick | UpdateFlags::Wait => match attr.time_slice.load(Relaxed) {
|
|
0 => (self.inactive_array().map.iter_ones().next())
|
|
.is_some_and(|prio| prio > usize::from(attr.prio.load(Relaxed))),
|
|
ts => ts <= rt.period_delta,
|
|
},
|
|
UpdateFlags::Yield => true,
|
|
}
|
|
}
|
|
}
|