avr-oxide 0.0.6

An extremely simple Rusty operating system for AVR microcontrollers
/* concurrency.rs
 *
 * Developed by Tim Walls <tim.walls@snowgoons.com>
 * Copyright (c) All Rights Reserved, Tim Walls
 */
//! Concurrency control primitives for the AVR microcontroller

// Imports ===================================================================

// Declarations ==============================================================

/**
 * All the context we store with a thread
 */
#[repr(C)]
#[derive(Clone,Copy)]
pub struct ThreadContext {
  entry_point: fn()->u8,

  // @todo in future we may want to do proper multithreading, in which case
  //       we need to include processor context and stack here
}

/**
 * A thread ID.  These are not unique over time, i.e. as threads are created
 * and destroyed these may be recycled.
 */
pub type ThreadId = u8;

// Code ======================================================================
pub(crate) mod internal {
  use crate::deviceconsts::oxide::MAX_THREADS;
  use crate::hal::concurrency::{ThreadContext, interrupt};

  /**
   * The current state of scheduled/running threads.
   */
  pub(super) struct SchedulerState<const MT: usize> {
    pub(super) threads: [Option<ThreadContext>; MT],
    pub(super) current_thread: Option<u8>,
    // @todo provide a suitable no-std static fifo implementation; until then
    //       our run queue is only one element long :-)
    pub(super) run_q: Option<u8>
  }

  pub(super) static mut SCHEDULER: Option<SchedulerState<MAX_THREADS>> = None;

  /**
   * Initialise the concurrency system.  Must be called once and only once,
   * and before interrupts have been enabled.
   */
  pub(crate) fn initialise() {
    unsafe {
      core::ptr::replace(&mut SCHEDULER,
                         Some(SchedulerState {
                           threads: [None; MAX_THREADS],
                           current_thread: None,
                           run_q: None
                         }));
    }
  }

  /**
   * Schedule threads for execution
   */
  pub(crate) unsafe fn run_scheduler() -> ! {
    loop {
      interrupt::disable_interrupts();

      if let Some(scheduler) = &mut SCHEDULER {
        if let Some(thread_id) = scheduler.run_q {
          scheduler.run_q          = None;
          scheduler.current_thread = Some(thread_id);

          if let Some(thread_context) = &mut scheduler.threads[thread_id as usize] {
            interrupt::enable_interrupts();
            (thread_context.entry_point)();
          } else {
            panic!();
          }
        } else {
          // No threads to execute.  Maybe if we reenable interrupts that
          // will change later
          interrupt::enable_interrupts();
          interrupt::wait();
        }
      } else {
        panic!()
      }
    }
  }
}

pub mod thread {
  use crate::hal::concurrency::{ThreadContext, interrupt, ThreadId};
  use crate::deviceconsts::oxide::MAX_THREADS;
  use crate::hal::concurrency::internal::SCHEDULER;

  /**
   * Handle that allows us to join a thread
   */
  pub struct JoinHandle {
    thread: Thread
  }

  /**
   * The 'userland facing' representation of a Thread
   */
  #[repr(C)]
  #[derive(Clone,Copy)]
  pub struct Thread {
    thread_id: u8
  }

  /**
   * Spawn a new thread, returning a JoinHandle for it.  Panics if the thread
   * cannot be spawned.
   */
  pub fn spawn(f: fn() -> u8) -> JoinHandle {
    interrupt::isolated(||{unsafe{spawn_threadunsafe(f)}})
  }

  /**
   * Spawn a new thread, returning a JoinHandle for it.  Panics if the thread
   * cannot be spawned.
   */
  pub(crate) unsafe fn spawn_threadunsafe(f: fn() -> u8) -> JoinHandle
  {
    if let Some(scheduler) = &mut SCHEDULER {
      for i in 0..MAX_THREADS {
        match scheduler.threads[i] {
          None => {
            let thread = ThreadContext {
              entry_point: f
            };
            scheduler.threads[i] = Some(thread);
            // Add to the run queue
            scheduler.run_q = Some(i as ThreadId);
            return JoinHandle {
              thread: Thread {
                thread_id: i as u8
              }
            };
          },
          Some(_) => {}
        }
      }
      // If we got here, no free threads
      panic!()
    } else {
      panic!()
    }
  }

  impl JoinHandle {
    /**
     * Wait for the associated thread to complete execution.
     */
    pub fn join(self) -> u8 {
      unimplemented!()
    }

    /**
     * Return a reference to the underlying thread object
     */
    pub fn thread(&self) -> &Thread {
      &self.thread
    }
  }

  impl Thread {
    /**
     * Get the thread's unique identifier.  Note that thread IDs are only
     * unique as long as the thread is running, and may be recycled.
     */
    pub fn id(&self) -> ThreadId {
      self.thread_id
    }
  }
}

pub mod interrupt {
  use crate::hal::generic::cpu::Cpu;

  /**
   * Execute the given closure without interruptions
   */
  pub fn isolated<F,R>(f: F) -> R
  where
    F: FnOnce() -> R
  {
    unsafe {
      // Save the current sreg and then disable interrupts
      #[cfg(target_arch="avr")]
      let sreg = crate::cpu!().read_sreg();

      #[cfg(not(target_arch="avr"))]
      println!("In isolated block");

      disable_interrupts();

      let result = f();

      // Don't explicitly re-enable interrupts, rather we just restore sreg
      #[cfg(target_arch="avr")]
      crate::cpu!().write_sreg(sreg);

      #[cfg(not(target_arch="avr"))]
      println!("Leaving isolated block");

      result
    }
  }

  /**
   * Execute the given code within the context of an Interrupt Service Routine.
   */
  #[inline(always)]
  pub(crate) fn isr<F,R>(f: F) -> R
  where
    F: FnOnce() -> R
  {
    // Actually does pretty much nothing at the moment, but keeping this around
    // because it may be useful in the future
    crate::hal::atmega4809::debugled::PinE2DebugLed::on();
    let result = f();
    crate::hal::atmega4809::debugled::PinE2DebugLed::off();
    result
  }

  #[cfg(not(target_arch="avr"))]
  pub(crate) unsafe fn enable_interrupts() {
  }
  #[cfg(not(target_arch="avr"))]
  pub(crate) unsafe fn disable_interrupts() {
  }

  use crate::hal::generic::debugled::DebugLed;
  #[cfg(target_arch="avr")]
  #[inline(always)]
  pub(crate) unsafe fn enable_interrupts(){
    llvm_asm!("sei" :::: "volatile")
  }

  #[cfg(target_arch="avr")]
  #[inline(always)]
  pub(crate) unsafe fn disable_interrupts() {
    llvm_asm!("cli" :::: "volatile")
  }

  #[cfg(not(target_arch="avr"))]
  pub fn wait() {
    std::thread::sleep(std::time::Duration::from_millis(100));
  }

  #[cfg(target_arch="avr")]
  #[inline(always)]
  pub fn wait() {
    unsafe {
      llvm_asm!("sleep");
    }
  }
}


// Tests =====================================================================