use crate::sync::atomic::{AtomicU8, AtomicUsize, Ordering};
pub const MAX_PTASKS: usize = crate::config::MAX_PTASKS;
pub const MAX_HELD: usize = crate::config::MAX_HELD;
pub const NO_TASK: usize = usize::MAX;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum TaskState {
Ready,
Running,
Blocked,
}
pub struct HeldMutex {
pub ptr: crate::sync::atomic::AtomicPtr<()>,
pub hwp: crate::sync::atomic::AtomicPtr<()>,
}
impl HeldMutex {
#[cfg(not(loom))]
pub const fn empty() -> Self {
Self::empty_impl()
}
#[cfg(loom)]
pub fn empty() -> Self {
Self::empty_impl()
}
#[cfg(not(loom))]
const fn empty_impl() -> Self {
Self {
ptr: crate::sync::atomic::AtomicPtr::new(core::ptr::null_mut()),
hwp: crate::sync::atomic::AtomicPtr::new(core::ptr::null_mut()),
}
}
#[cfg(loom)]
fn empty_impl() -> Self {
Self {
ptr: crate::sync::atomic::AtomicPtr::new(core::ptr::null_mut()),
hwp: crate::sync::atomic::AtomicPtr::new(core::ptr::null_mut()),
}
}
}
pub struct Tcb {
pub sp: AtomicUsize,
pub base_priority: AtomicU8,
pub effective_priority: AtomicU8,
pub state: crate::sync::atomic::AtomicU8, pub used: crate::sync::atomic::AtomicBool,
pub stack_base: crate::sync::atomic::AtomicUsize,
pub stack_size: crate::sync::atomic::AtomicUsize,
pub held: [HeldMutex; MAX_HELD],
pub held_count: crate::sync::atomic::AtomicU8,
pub last_checkin: crate::sync::atomic::AtomicU32,
pub generation: crate::sync::atomic::AtomicU32,
pub result_buf: core::cell::UnsafeCell<[u8; 32]>,
pub result_size: crate::sync::atomic::AtomicU8,
pub result_drop: crate::sync::atomic::AtomicUsize,
pub exited: crate::sync::atomic::AtomicBool,
pub joiner: crate::sync::atomic::AtomicUsize,
pub stop_requested: crate::sync::atomic::AtomicBool,
}
pub(crate) const READY: u8 = 0;
pub(crate) const RUNNING: u8 = 1;
pub(crate) const BLOCKED: u8 = 2;
pub(crate) const RESERVED: u8 = 3;
pub(crate) const SUSPENDED: u8 = 4;
impl Tcb {
pub fn stack_info(&self) -> Option<(usize, usize)> {
let base = self.stack_base.load(Ordering::Acquire);
let size = self.stack_size.load(Ordering::Acquire);
if base == 0 || size == 0 {
None
} else {
Some((base, size))
}
}
#[cfg(not(loom))]
pub const fn new() -> Self {
Self {
sp: AtomicUsize::new(0),
base_priority: AtomicU8::new(0),
effective_priority: AtomicU8::new(0),
state: crate::sync::atomic::AtomicU8::new(READY),
used: crate::sync::atomic::AtomicBool::new(false),
stack_base: crate::sync::atomic::AtomicUsize::new(0),
stack_size: crate::sync::atomic::AtomicUsize::new(0),
held: [const { HeldMutex::empty() }; MAX_HELD],
held_count: crate::sync::atomic::AtomicU8::new(0),
last_checkin: crate::sync::atomic::AtomicU32::new(0),
generation: crate::sync::atomic::AtomicU32::new(0),
result_buf: core::cell::UnsafeCell::new([0u8; 32]),
result_size: crate::sync::atomic::AtomicU8::new(0),
result_drop: crate::sync::atomic::AtomicUsize::new(0),
exited: crate::sync::atomic::AtomicBool::new(false),
joiner: crate::sync::atomic::AtomicUsize::new(NO_TASK),
stop_requested: crate::sync::atomic::AtomicBool::new(false),
}
}
#[cfg(loom)]
pub fn new() -> Self {
Self {
sp: AtomicUsize::new(0),
base_priority: AtomicU8::new(0),
effective_priority: AtomicU8::new(0),
state: crate::sync::atomic::AtomicU8::new(READY),
used: crate::sync::atomic::AtomicBool::new(false),
stack_base: crate::sync::atomic::AtomicUsize::new(0),
stack_size: crate::sync::atomic::AtomicUsize::new(0),
held: core::array::from_fn(|_| HeldMutex::empty()),
held_count: crate::sync::atomic::AtomicU8::new(0),
last_checkin: crate::sync::atomic::AtomicU32::new(0),
generation: crate::sync::atomic::AtomicU32::new(0),
result_buf: core::cell::UnsafeCell::new([0u8; 32]),
result_size: crate::sync::atomic::AtomicU8::new(0),
result_drop: crate::sync::atomic::AtomicUsize::new(0),
exited: crate::sync::atomic::AtomicBool::new(false),
joiner: crate::sync::atomic::AtomicUsize::new(NO_TASK),
stop_requested: crate::sync::atomic::AtomicBool::new(false),
}
}
pub fn push_held(&self, ptr: *const (), hwp: fn(*const ()) -> u8) -> bool {
if self.held_count.load(crate::sync::atomic::Ordering::Acquire) as usize >= MAX_HELD {
return false;
}
for slot in &self.held {
if slot
.ptr
.load(crate::sync::atomic::Ordering::Acquire)
.is_null()
{
slot.hwp.store(
hwp as *const () as *mut (),
crate::sync::atomic::Ordering::Release,
);
slot.ptr
.store(ptr as *mut (), crate::sync::atomic::Ordering::Release);
self.held_count
.fetch_add(1, crate::sync::atomic::Ordering::Release);
return true;
}
}
false
}
pub fn remove_held(&self, ptr: *const ()) {
for slot in &self.held {
let loaded = slot.ptr.load(crate::sync::atomic::Ordering::Acquire);
if core::ptr::eq(loaded, ptr) {
slot.ptr.store(
core::ptr::null_mut(),
crate::sync::atomic::Ordering::Release,
);
self.held_count
.fetch_sub(1, crate::sync::atomic::Ordering::Release);
return;
}
}
}
pub fn state(&self) -> TaskState {
match self.state.load(Ordering::Acquire) {
RUNNING => TaskState::Running,
BLOCKED => TaskState::Blocked,
_ => TaskState::Ready,
}
}
pub fn set_state(&self, id: usize, s: TaskState) {
let v = match s {
TaskState::Ready => READY,
TaskState::Running => RUNNING,
TaskState::Blocked => BLOCKED,
};
self.state.store(v, Ordering::Release);
match s {
TaskState::Ready => crate::preempt::sched::ready_add(id),
TaskState::Running | TaskState::Blocked => crate::preempt::sched::ready_remove(id),
}
}
pub fn set_effective_priority(&self, id: usize, new: u8) {
let old = self.effective_priority.load(Ordering::Acquire);
self.effective_priority.store(new, Ordering::Release);
crate::preempt::sched::on_effective_priority_change(id, old, new);
}
}
unsafe impl Sync for Tcb {}
impl Default for Tcb {
fn default() -> Self {
Self::new()
}
}
#[cfg(not(loom))]
pub static TASKS: [Tcb; MAX_PTASKS] = [const { Tcb::new() }; MAX_PTASKS];
#[cfg(loom)]
loom::lazy_static! {
pub static ref TASKS: [Tcb; MAX_PTASKS] = core::array::from_fn(|_| Tcb::new());
}
pub fn register_full(
sp: usize,
priority: u8,
stack_base: usize,
stack_size: usize,
) -> Option<usize> {
for (id, tcb) in TASKS.iter().enumerate() {
if tcb.used.load(Ordering::Acquire) {
continue;
}
if tcb
.state
.compare_exchange(READY, RESERVED, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
tcb.sp.store(sp, Ordering::Release);
tcb.base_priority.store(priority, Ordering::Release);
tcb.effective_priority.store(priority, Ordering::Release);
tcb.stack_base.store(stack_base, Ordering::Release);
tcb.stack_size.store(stack_size, Ordering::Release);
let drop_fn = tcb.result_drop.load(Ordering::Acquire);
if drop_fn != 0 {
let f: fn(*mut u8) = unsafe { core::mem::transmute(drop_fn) };
f(tcb.result_buf.get() as *mut u8);
tcb.result_drop.store(0, Ordering::Release);
}
tcb.joiner.store(NO_TASK, Ordering::Release);
tcb.exited.store(false, Ordering::Release);
tcb.stop_requested.store(false, Ordering::Release);
tcb.result_size.store(0, Ordering::Release);
tcb.held_count.store(0, Ordering::Release);
tcb.state.store(READY, Ordering::Release);
tcb.used.store(true, Ordering::Release);
tcb.generation.fetch_add(1, Ordering::Release);
crate::preempt::sched::ready_add(id);
return Some(id);
}
}
None
}
pub fn register(sp: usize, priority: u8) -> Option<usize> {
register_full(sp, priority, 0, 0)
}
pub fn get(id: usize) -> Option<&'static Tcb> {
TASKS.get(id).filter(|t| t.used.load(Ordering::Acquire))
}
#[cfg(feature = "test-support")]
pub(crate) fn reset_for_test() {
for tcb in TASKS.iter() {
tcb.used.store(false, Ordering::Release);
tcb.state.store(READY, Ordering::Release);
tcb.sp.store(0, Ordering::Release);
tcb.base_priority.store(0, Ordering::Release);
tcb.effective_priority.store(0, Ordering::Release);
tcb.exited.store(false, Ordering::Release);
tcb.result_size.store(0, Ordering::Release);
tcb.result_drop.store(0, Ordering::Release);
tcb.joiner.store(NO_TASK, Ordering::Release);
tcb.stop_requested.store(false, Ordering::Release);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn register_assigns_first_free_slot_and_sets_fields() {
crate::kernel_test! {
let a = register(0x1000, 3).unwrap();
assert_eq!(a, 0, "first free slot is 0");
let b = register(0x2000, 5).unwrap();
assert_eq!(b, 1);
let ta = get(a).unwrap();
assert_eq!(ta.sp.load(Ordering::Acquire), 0x1000);
assert_eq!(ta.base_priority.load(Ordering::Acquire), 3);
assert_eq!(ta.effective_priority.load(Ordering::Acquire), 3);
assert_eq!(ta.state(), TaskState::Ready);
}
}
#[test]
fn register_full_returns_none() {
crate::kernel_test! {
for i in 0..MAX_PTASKS {
assert!(register(0x1000 + i, 1).is_some(), "slot {i}");
}
assert_eq!(register(0x9000, 1), None, "registry full");
}
}
}