use core::mem::MaybeUninit;
use avr_oxide::{cpu, OxideResult, thread};
use avr_oxide::deviceconsts::oxide::MAX_THREADS;
use avr_oxide::hal::generic::cpu::{ContextSaveRestore, Cpu};
use avr_oxide::util::datatypes::Volatile;
use avr_oxide::alloc::boxed::Box;
use avr_oxide::hal::generic::cpu::ProcessorContext;
use avr_oxide::concurrency::{interrupt, stack};
use avr_oxide::concurrency::util::{ThreadId, ThreadSet};
use avr_oxide::concurrency::stack::ThreadStack;
use avr_oxide::oserror::OsError;
use avr_oxide::OxideResult::{Ok,Err};
const IDLE_THREAD_STACK_SIZE: usize = 48;
pub(super) struct SchedulerState<const MT: usize> {
pub(super) threads: [Option<ThreadContext>; MT],
}
static mut SCHEDULER: MaybeUninit<SchedulerState<MAX_THREADS>> = MaybeUninit::uninit();
#[cfg(target_arch="avr")]
extern {
static mut __NEXT_THREAD_CONTEXT : Volatile<usize>;
}
#[cfg(not(target_arch="avr"))]
static mut __NEXT_THREAD_CONTEXT : Volatile<usize> = Volatile::<usize>::zero();
#[repr(C)]
pub struct ThreadContext {
pub(crate) cpu_context: ProcessorContext,
pub(crate) guard: Volatile<u8>,
pub(crate) state: ThreadState,
pub(crate) stack: Option<Box<dyn ThreadStack>>,
pub(crate) entrypoint: Option<Box<dyn FnOnce()->u8>>,
pub(crate) returncode: u8,
pub(crate) waiting_threads: ThreadSet
}
#[derive(PartialEq,Copy,Clone)]
pub enum ThreadState {
Schedulable,
BackgroundSchedulable,
Suspended,
Zombie,
Dead,
BlockedOnMutex,
BlockedOnThread,
BlockedOnQueue,
BlockedOnEvent,
}
pub(super) unsafe fn instance() -> &'static mut SchedulerState<MAX_THREADS> {
SCHEDULER.assume_init_mut()
}
fn idle_thread() -> u8 {
interrupt::isolated(|isotoken|{
set_current_thread_state(isotoken, ThreadState::BackgroundSchedulable);
});
loop {
unsafe {
thread::yield_now();
core::arch::asm!(
" sbic {context_flags_reg},{flag_preemption}",
" sleep",
" cbi {context_flags_reg},{flag_preemption}",
context_flags_reg = const(avr_oxide::hardware::cpu::cpuregs::IOADR_CONTEXT_FLAGS),
flag_preemption = const(avr_oxide::hal::generic::cpu::CONTEXT_FLAG_PREEMPTION),
);
}
}
}
impl ThreadState {
fn change(&mut self, new_state: ThreadState) {
#[cfg(feature="runtime_checks")]
if cpu!().in_isr() {
if new_state == Self::BlockedOnMutex {
avr_oxide::oserror::halt(OsError::BadThreadState);
}
if new_state == Self::BlockedOnQueue {
avr_oxide::oserror::halt(OsError::BadThreadState);
}
if new_state == Self::BlockedOnEvent {
avr_oxide::oserror::halt(OsError::BadThreadState);
}
if new_state == Self::BlockedOnThread {
avr_oxide::oserror::halt(OsError::BadThreadState);
}
}
#[cfg(feature="runtime_checks")]
if *self == Self::BackgroundSchedulable {
avr_oxide::oserror::halt(OsError::BadThreadState);
}
*self = new_state;
}
fn can_be_reaped(&self) -> bool {
*self == Self::Dead
}
pub(crate) fn to_debug_str(&self) -> &str {
match self {
ThreadState::Schedulable => "RDY",
ThreadState::BackgroundSchedulable => "bgd",
ThreadState::Suspended => "Sus",
ThreadState::Zombie => "Zby",
ThreadState::Dead => "XXX",
ThreadState::BlockedOnMutex => "B:M",
ThreadState::BlockedOnThread => "B:T",
ThreadState::BlockedOnQueue => "B:Q",
ThreadState::BlockedOnEvent => "B:E",
}
}
}
pub(crate) fn initialise() {
unsafe {
const NOTHREAD: Option<ThreadContext> = None;
core::ptr::write(SCHEDULER.as_mut_ptr(),
SchedulerState {
threads: [NOTHREAD; MAX_THREADS],
});
stack::kernel::initialise();
}
thread::Builder::new().stack_size(IDLE_THREAD_STACK_SIZE).spawn(idle_thread);
}
pub(crate) unsafe fn get_current_thread(isotoken: interrupt::token::Isolated) -> &'static mut ThreadContext {
core::mem::transmute(cpu!().get_processor_context(isotoken))
}
pub(crate) unsafe fn get_thread_by_id(thread_id: ThreadId) -> &'static mut ThreadContext {
let scheduler = SCHEDULER.assume_init_mut();
match &mut scheduler.threads[thread_id] {
None => {
avr_oxide::oserror::halt(avr_oxide::oserror::OsError::InternalError);
},
Some(thread) => {
thread
}
}
}
pub(crate) unsafe fn try_get_thread_by_id(thread_id: ThreadId) -> &'static mut Option<ThreadContext> {
let scheduler = SCHEDULER.assume_init_mut();
&mut scheduler.threads[thread_id]
}
pub fn current_thread_id(isotoken: interrupt::token::Isolated) -> ThreadId {
unsafe {
cpu!().get_processor_context(isotoken).tid
}
}
pub(crate) fn set_current_thread_state(isotoken: interrupt::token::Isolated, new_state: ThreadState) {
unsafe {
get_current_thread(isotoken).state.change(new_state);
}
}
pub(crate) fn try_set_thread_state(_isotoken: interrupt::token::Isolated, thread_id: ThreadId, new_state: ThreadState) -> OxideResult<(),OsError> {
unsafe {
match try_get_thread_by_id(thread_id) {
Some(thread) => {
thread.state.change(new_state);
Ok(())
},
None => {
Err(OsError::NoSchedulableThreads)
}
}
}
}
pub(crate) fn set_thread_state(_isotoken: interrupt::token::Isolated, thread_id: ThreadId, new_state: ThreadState) {
unsafe {
get_thread_by_id(thread_id).state.change(new_state);
}
}
pub(crate) fn release_all_threads_and_clear(isotoken: interrupt::token::Isolated, threads: &mut ThreadSet) {
threads.do_each_consuming(isotoken, |isotoken, thread_id|{
let _ = try_set_thread_state(isotoken, thread_id, ThreadState::Schedulable);
true
});
}
impl ThreadContext {
pub(crate) fn halt_if_invalid(&self) {
if self.guard.read() != 0xf0 {
avr_oxide::oserror::halt(OsError::KernelGuardCrashed);
}
}
}
pub(super) unsafe extern "C" fn thread_entrypoint() -> () {
#[cfg(target_arch="avr")]
core::arch::asm!(
" sbic {context_flags_reg},{flag_enableints}", " sei",
" sbic {context_flags_reg},{flag_reti}", " call 1f",
" jmp 2f",
"1:", "reti",
"2:", "nop",
context_flags_reg = const(avr_oxide::hardware::cpu::cpuregs::IOADR_CONTEXT_FLAGS),
flag_enableints = const(avr_oxide::hal::generic::cpu::CONTEXT_FLAG_ENABLEINTS),
flag_reti = const(avr_oxide::hal::generic::cpu::CONTEXT_FLAG_RETI)
);
let thread = avr_oxide::concurrency::interrupt::isolated(|isotoken|{
get_current_thread(isotoken)
});
let code = thread.entrypoint.take().unwrap();
let res = (code)();
avr_oxide::concurrency::interrupt::isolated(|isotoken|{
thread.returncode = res;
thread.state.change(ThreadState::Zombie);
core::mem::drop(thread.stack.take());
release_all_threads_and_clear(isotoken, &mut thread.waiting_threads);
});
userland_schedule_and_switch();
}
#[inline(never)] unsafe fn check_all_threads_valid() {
for i in 0..MAX_THREADS {
if let Some(thread) = &instance().threads[i] {
thread.halt_if_invalid();
match &thread.stack {
Some(stack) => {
stack.halt_if_stack_crashed();
},
None => {}
}
}
}
}
pub(crate) fn schedule_next_thread(isotoken: avr_oxide::concurrency::interrupt::token::Isolated) {
unsafe {
#[cfg(feature="runtime_checks")]
check_all_threads_valid();
let mut candidate = current_thread_id(isotoken);
for _i in 0..MAX_THREADS {
candidate = (candidate + 1) % MAX_THREADS;
if let Some(thread_context) = &mut instance().threads[candidate] {
if thread_context.state == ThreadState::Schedulable {
__NEXT_THREAD_CONTEXT.write_isolated(isotoken, &mut thread_context.cpu_context as *mut ProcessorContext as usize);
return;
}
}
}
for _i in 0..MAX_THREADS {
candidate = (candidate + 1) % MAX_THREADS;
if let Some(thread_context) = &mut instance().threads[candidate] {
if thread_context.state == ThreadState::BackgroundSchedulable {
__NEXT_THREAD_CONTEXT.write_isolated(isotoken, &mut thread_context.cpu_context as *mut ProcessorContext as usize);
return;
}
}
}
avr_oxide::oserror::halt(avr_oxide::oserror::OsError::NoSchedulableThreads);
}
}
pub(crate) fn reap_dead_threads(_isotoken: interrupt::token::Isolated) {
unsafe {
let scheduler = SCHEDULER.assume_init_mut();
for i in 0..MAX_THREADS {
match &scheduler.threads[i] {
None => {},
Some(thread) => {
if thread.state.can_be_reaped() {
let farewell = scheduler.threads[i].take();
core::mem::drop(farewell);
}
}
}
}
}
}
pub(crate) unsafe extern "C" fn userland_schedule_and_switch() {
interrupt::isolated(|isotoken|{
schedule_next_thread(isotoken);
#[cfg(target_arch="avr")]
core::arch::asm!(
" call save_thread_context",
" sbic {context_flags_reg},{flag_restored}",
" jmp 1f",
" jmp restore_thread_context",
"1:", " sbic {context_flags_reg},{flag_enableints}", " sei",
" sbic {context_flags_reg},{flag_reti}", " call 1f",
" jmp 2f",
"1:","reti",
"2:","nop",
context_flags_reg = const(avr_oxide::hardware::cpu::cpuregs::IOADR_CONTEXT_FLAGS),
flag_enableints = const(avr_oxide::hal::generic::cpu::CONTEXT_FLAG_ENABLEINTS),
flag_reti = const(avr_oxide::hal::generic::cpu::CONTEXT_FLAG_RETI),
flag_restored = const(avr_oxide::hal::generic::cpu::CONTEXT_FLAG_RESTORED),
options(preserves_flags)
);
});
}
pub(crate) unsafe fn restore_first_thread() -> ! {
if let Some(thread_context) = &mut instance().threads[0] {
__NEXT_THREAD_CONTEXT.write(&mut thread_context.cpu_context as *mut ProcessorContext as usize);
#[cfg(target_arch="avr")]
core::arch::asm!(
" jmp restore_thread_context",
options(noreturn));
#[cfg(not(target_arch="avr"))]
core::arch::asm!("nop",options(noreturn));
} else {
avr_oxide::oserror::halt(avr_oxide::oserror::OsError::NoSchedulableThreads);
}
}