use core::cell::UnsafeCell;
use crate::preempt::tcb::MAX_PTASKS;
use crate::sync::atomic::{AtomicU32, Ordering};
struct PeriodSlot {
next_us: UnsafeCell<u64>,
budget_start_cycles: UnsafeCell<u64>,
}
unsafe impl Sync for PeriodSlot {}
#[cfg(not(loom))]
static PERIOD_US: [AtomicU32; MAX_PTASKS] = [const { AtomicU32::new(0) }; MAX_PTASKS];
#[cfg(loom)]
loom::lazy_static! {
static ref PERIOD_US: [AtomicU32; MAX_PTASKS] = core::array::from_fn(|_| AtomicU32::new(0));
}
#[cfg(not(loom))]
static BUDGET_US: [AtomicU32; MAX_PTASKS] = [const { AtomicU32::new(0) }; MAX_PTASKS];
#[cfg(loom)]
loom::lazy_static! {
static ref BUDGET_US: [AtomicU32; MAX_PTASKS] = core::array::from_fn(|_| AtomicU32::new(0));
}
static SLOTS: [PeriodSlot; MAX_PTASKS] = [const {
PeriodSlot {
next_us: UnsafeCell::new(0),
budget_start_cycles: UnsafeCell::new(0),
}
}; MAX_PTASKS];
pub fn set_period_us(id: usize, period_us: u32) {
if let Some(slot) = PERIOD_US.get(id) {
slot.store(period_us, Ordering::Release);
}
}
pub fn set_budget_us(id: usize, budget_us: u32) {
if let Some(slot) = BUDGET_US.get(id) {
slot.store(budget_us, Ordering::Release);
}
}
pub fn period_us(id: usize) -> u32 {
PERIOD_US.get(id).map_or(0, |s| s.load(Ordering::Acquire))
}
pub fn budget_us(id: usize) -> u32 {
BUDGET_US.get(id).map_or(0, |s| s.load(Ordering::Acquire))
}
pub fn wait_period() {
let Some(me) = crate::preempt::sched::current() else {
return;
};
let period = period_us(me) as u64;
if period == 0 {
return;
}
let Some(slot) = SLOTS.get(me) else {
return;
};
let next = crate::critical::enter(|| {
unsafe {
let prev = *slot.next_us.get();
let next = if prev == 0 {
crate::port::board::now_us().wrapping_add(period)
} else {
prev.wrapping_add(period)
};
*slot.next_us.get() = next;
*slot.budget_start_cycles.get() = crate::exec_time::busy_cycles(me);
next
}
});
crate::preempt::sleep_until(next);
}
pub(crate) fn check_budget(id: usize) -> bool {
let budget = budget_us(id);
if budget == 0 {
return false;
}
let Some(slot) = SLOTS.get(id) else {
return false;
};
let start = crate::critical::enter(|| unsafe { *slot.budget_start_cycles.get() });
let used_cycles = crate::exec_time::busy_cycles_live(id).wrapping_sub(start);
let used_us = crate::exec_time::estimate_us_from_cycles(used_cycles);
used_us > budget as u64
}
#[cfg(feature = "test-support")]
pub(crate) fn reset_for_test() {
for s in PERIOD_US.iter() {
s.store(0, Ordering::Relaxed);
}
for s in BUDGET_US.iter() {
s.store(0, Ordering::Relaxed);
}
crate::critical::enter(|| {
for slot in SLOTS.iter() {
unsafe {
*slot.next_us.get() = 0;
*slot.budget_start_cycles.get() = 0;
}
}
});
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wait_period_noop_without_config() {
crate::kernel_test! {
wait_period();
}
}
#[test]
fn check_budget_false_without_config() {
crate::kernel_test! {
assert!(!check_budget(0));
}
}
#[test]
fn set_and_read_period_budget() {
crate::kernel_test! {
set_period_us(2, 5000);
set_budget_us(2, 1000);
assert_eq!(period_us(2), 5000);
assert_eq!(budget_us(2), 1000);
}
}
}