avr-oxide 0.2.2

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 avr_oxide::deviceconsts::oxide::MAX_THREADS;
  use avr_oxide::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 avr_oxide::hal::concurrency::{ThreadContext, interrupt, ThreadId};
  use avr_oxide::deviceconsts::oxide::MAX_THREADS;
  use avr_oxide::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 avr_oxide::hal::generic::cpu::Cpu;
  use avr_oxide::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 = cpu!().read_sreg();

      disable_interrupts();

      let result = f();

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

      result
    }
  }

  /**
   * Execute the given code within the context of an Interrupt Service Routine.
   */
  #[inline(always)]
  pub(crate) fn isr<F>(f: F) -> ()
  where
    F: FnOnce() -> (),
  {
    // @todo: OK, so this is quite clearly absolutely horrendous.
    //        I need to find out exactly what code generation bug still
    //        remains that LLVM isn't generating proper register saves
    //        on interrupt service routines, and fix it.  Because obviously
    //        saving and restoring every single register on every interrupt
    //        is just painful.  On the other hand, it does make things
    //        actually functional...
    #[allow(deprecated)]
    unsafe {
      llvm_asm!("push r24" :::: "volatile");
      #[cfg(feature="atmega4809")]
      llvm_asm!("in r24,0x0F" :::: "volatile");
      #[cfg(feature="atmega328p")]
      llvm_asm!("in r24,0x3F" :::: "volatile");
      llvm_asm!("push r24" :::: "volatile");

      llvm_asm!("push r31" :::: "volatile");
      llvm_asm!("push r30" :::: "volatile");
      llvm_asm!("push r29" :::: "volatile");
      llvm_asm!("push r28" :::: "volatile");
      llvm_asm!("push r27" :::: "volatile");
      llvm_asm!("push r26" :::: "volatile");
      llvm_asm!("push r25" :::: "volatile");

      llvm_asm!("push r23" :::: "volatile");
      llvm_asm!("push r22" :::: "volatile");
      llvm_asm!("push r21" :::: "volatile");
      llvm_asm!("push r20" :::: "volatile");
      llvm_asm!("push r19" :::: "volatile");
      llvm_asm!("push r18" :::: "volatile");
      llvm_asm!("push r17" :::: "volatile");
      llvm_asm!("push r16" :::: "volatile");
      llvm_asm!("push r15" :::: "volatile");
      llvm_asm!("push r14" :::: "volatile");
      llvm_asm!("push r13" :::: "volatile");
      llvm_asm!("push r12" :::: "volatile");
      llvm_asm!("push r11" :::: "volatile");
      llvm_asm!("push r10" :::: "volatile");
      llvm_asm!("push r9" :::: "volatile");
      llvm_asm!("push r8" :::: "volatile");
      llvm_asm!("push r7" :::: "volatile");
      llvm_asm!("push r6" :::: "volatile");
      llvm_asm!("push r5" :::: "volatile");
      llvm_asm!("push r4" :::: "volatile");
      llvm_asm!("push r3" :::: "volatile");
      llvm_asm!("push r2" :::: "volatile");
      llvm_asm!("push r1" :::: "volatile");
      llvm_asm!("push r0" :::: "volatile");

      f();

      llvm_asm!("pop r0" :::: "volatile");
      llvm_asm!("pop r1" :::: "volatile");
      llvm_asm!("pop r2" :::: "volatile");
      llvm_asm!("pop r3" :::: "volatile");
      llvm_asm!("pop r4" :::: "volatile");
      llvm_asm!("pop r5" :::: "volatile");
      llvm_asm!("pop r6" :::: "volatile");
      llvm_asm!("pop r7" :::: "volatile");
      llvm_asm!("pop r8" :::: "volatile");
      llvm_asm!("pop r9" :::: "volatile");
      llvm_asm!("pop r10" :::: "volatile");
      llvm_asm!("pop r11" :::: "volatile");
      llvm_asm!("pop r12" :::: "volatile");
      llvm_asm!("pop r13" :::: "volatile");
      llvm_asm!("pop r14" :::: "volatile");
      llvm_asm!("pop r15" :::: "volatile");
      llvm_asm!("pop r16" :::: "volatile");
      llvm_asm!("pop r17" :::: "volatile");
      llvm_asm!("pop r18" :::: "volatile");
      llvm_asm!("pop r19" :::: "volatile");
      llvm_asm!("pop r20" :::: "volatile");
      llvm_asm!("pop r21" :::: "volatile");
      llvm_asm!("pop r22" :::: "volatile");
      llvm_asm!("pop r23" :::: "volatile");

      llvm_asm!("pop r25" :::: "volatile");
      llvm_asm!("pop r26" :::: "volatile");
      llvm_asm!("pop r27" :::: "volatile");
      llvm_asm!("pop r28" :::: "volatile");
      llvm_asm!("pop r29" :::: "volatile");
      llvm_asm!("pop r30" :::: "volatile");
      llvm_asm!("pop r31" :::: "volatile");

      llvm_asm!("pop r24" :::: "volatile");
      #[cfg(feature="atmega4809")]
      llvm_asm!("out 0x0F,r24" :::: "volatile");
      #[cfg(feature="atmega328p")]
      llvm_asm!("out 0x3F,r24" :::: "volatile");
      llvm_asm!("pop r24" :::: "volatile");
    }
  }

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


  #[cfg(target_arch="avr")]
  #[inline(always)]
  pub(crate) unsafe fn enable_interrupts(){
    #![allow(deprecated)] // until asm! is implemented for AVR
    llvm_asm!("sei" :::: "volatile")
  }

  #[cfg(target_arch="avr")]
  #[inline(always)]
  pub(crate) unsafe fn disable_interrupts() {
    #![allow(deprecated)] // until asm! is implemented for AVR
    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 {
      #![allow(deprecated)] // until asm! is implemented for AVR
      llvm_asm!("sleep");
    }
  }
}


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