avr-oxide 0.3.1

An extremely simple Rusty operating system for AVR microcontrollers
/* stack.rs
 *
 * Developed by Tim Walls <tim.walls@snowgoons.com>
 * Copyright (c) All Rights Reserved, Tim Walls
 */
//! Implementations of stack allocations for threads.

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

use core::alloc::Layout;
use avr_oxide::pie;
use avr_oxide::hal::generic::cpu::Cpu;

// Declarations ==============================================================
const STACKGUARD:u8 = 0b10101010;

/// A stack that can be used by a thread
pub(crate) trait ThreadStack {
  /// Return the address of the top byte of the stack
  fn get_stack_top(&self) -> usize;

  /// Check if the stack guards have been corrupted
  fn is_stack_guard_valid(&self) -> bool;

  /// Check if the stack pointer is currently within the bounds of this stack.
  fn is_sp_within(&self, isotoken: avr_oxide::concurrency::interrupt::token::Isolated) -> bool;

  /// Halt if the stack pointer is not within the bounds of this stack.
  fn halt_if_not_sp_within(&self, isotoken: avr_oxide::concurrency::interrupt::token::Isolated) {
    if !self.is_sp_within(isotoken) {
      avr_oxide::oserror::halt(avr_oxide::oserror::OsError::StackOverflow);
    }
  }

  /// Halt if stack guards have been corrupted
  fn halt_if_stack_crashed(&self) {
    if !self.is_stack_guard_valid() {
      avr_oxide::oserror::halt(avr_oxide::oserror::OsError::StackOverflow);
    }
  }

  /// Indicate how much of the stack has not been written to yet
  fn get_stack_free(&self) -> usize;

  /// Return the size of the stack
  fn get_size(&self) -> usize;
}


/**
 * A simple statically-allocated stack
 */
#[repr(C)]
pub struct StaticThreadStack<const STACK_SIZE:usize> {
  loguard: u8,
  stack: [u8; STACK_SIZE],
  higuard: u8
}

/**
 * A dynamically allocated stack that lives on the heap
 */
pub struct DynamicThreadStack {
  size: usize,
  space: *mut u8
}

// Code ======================================================================
impl DynamicThreadStack {
  pub fn new(size: usize) -> Self {
    unsafe {
      let region = avr_oxide::alloc::alloc(pie!(Layout::from_size_align(size+2, 1)));

      *(region.offset(0)) = STACKGUARD;
      for offset in 1..=size {
        *(region.offset(offset as isize)) = 0xD5;
      }
      *(region.offset((size+1) as isize)) = STACKGUARD;

      DynamicThreadStack {
        size,
        space: region
      }
    }
  }
}

impl Drop for DynamicThreadStack {
  /// Take care to deallocate the manually allocated RAM block when we are
  /// dropped.
  fn drop(&mut self) {
    unsafe {
      avr_oxide::alloc::dealloc(self.space, pie!(Layout::from_size_align(self.size+2,1)));
    }
  }
}

impl<const STACK_SIZE:usize> StaticThreadStack<STACK_SIZE> {
  #[allow(dead_code)]
  pub const fn new() -> Self {
    StaticThreadStack {
      loguard: STACKGUARD,
      stack:   [0x55; STACK_SIZE],
      higuard: STACKGUARD
    }
  }
}

impl<const STACK_SIZE:usize> ThreadStack for StaticThreadStack<STACK_SIZE> {
  /// Return the address of the top byte of the stack
  fn get_stack_top(&self) -> usize {
    self as *const StaticThreadStack<STACK_SIZE> as usize + STACK_SIZE
  }

  /// Check if the stack guards have been corrupted
  #[inline(always)]
  fn is_stack_guard_valid(&self) -> bool {
    self.loguard == STACKGUARD && self.higuard == STACKGUARD
  }

  fn is_sp_within(&self, _isotoken: avr_oxide::concurrency::interrupt::token::Isolated) -> bool {
    let sp = avr_oxide::cpu!().read_sp() as usize;

    sp > (&self.loguard as *const u8 as usize) && sp < (&self.higuard as *const u8 as usize)
  }

  fn get_stack_free(&self) -> usize {
    for count in 0..STACK_SIZE {
      if self.stack[count] != 0x55 {
        return count;
      }
    }
    STACK_SIZE
  }

  fn get_size(&self) -> usize {
    STACK_SIZE
  }
}

impl ThreadStack for DynamicThreadStack {
  fn get_stack_top(&self) -> usize {
    unsafe {
      // Note we offset by size, not size-1, because of the bottom guard
      // byte at location 0
      self.space.offset(self.size as isize) as usize
    }
  }

  #[inline(always)]
  fn is_stack_guard_valid(&self) -> bool {
    unsafe {
      *self.space.offset(0) == STACKGUARD && *self.space.offset((self.size+1) as isize) == STACKGUARD
    }
  }

  fn is_sp_within(&self, _isotoken: avr_oxide::concurrency::interrupt::token::Isolated) -> bool {
    let sp = avr_oxide::cpu!().read_sp() as usize;

    sp > (self.space as usize) && sp <= ((self.space as usize) + self.size)
  }

  fn get_stack_free(&self) -> usize {
    for count in 0..self.size {
      unsafe {
        if *self.space.offset((count+1) as isize) != 0xD5 {
          return count;
        }
      }
    }
    self.size
  }

  fn get_size(&self) -> usize {
    self.size
  }
}