avr-oxide 0.4.0

An extremely simple Rusty operating system for AVR microcontrollers
/* oserror
 *
 * Developed by Tim Walls <tim.walls@snowgoons.com>
 * Copyright (c) All Rights Reserved, Tim Walls
 */
//! A way of communicating fatal OS errors that doesn't have all the
//! baggage that Rust loads Panic with, but still allows for useful debugging.
//!
//! You can denote a pin (typically an error LED or something similar that
//! you can hook up a meter to), which will be used to signal error
//! conditions as a series of pulses.
//!
//! When the OS triggers these conditions it is fatal; it will sit in a loop
//! flashing the given pin until the device is reset.  If you need to
//! automatically reset after an error, use the Watchdog device to arrange
//! this.
//!
//! # Setting the debug pin to use
//! The debug pin to use can be set dynamically at runtime using the
//! `set_debug_pin` method:
//!
//! ```rust,no_run
//! # fn set_debug_pin() {
//!     avr_oxide::oserror::set_debug_pin(avr_oxide::hal::atmega4809::port::porte::pin(2));
//! # }
//! ```

// Imports ===================================================================
use avr_oxide::hal::generic::port::{InterruptMode, Pin, PinMode};
use avr_oxide::util::debug;
use core::ops::{ControlFlow, Try};

// Declarations ==============================================================
static mut DEBUGPIN : Option<&'static dyn Pin> = None;

/// A type used for returning and propagating errors; this implementation
/// differs from `core::result::Result` by having an `unwrap()`
/// implementation that doesn't require Debug and importing a thousand
/// dependencies we can't afford on AVR.
#[cfg_attr(not(target_arch="avr"), derive(Debug))]
#[cfg_attr(target_arch="avr", derive(ufmt::derive::uDebug))]
pub enum OxideResult<T,E> {
  Ok(T),
  Err(E)
}

#[cfg_attr(not(target_arch="avr"), derive(Debug))]
#[cfg_attr(target_arch="avr", derive(ufmt::derive::uDebug))]
#[derive(Copy,Clone,PartialEq)]
pub enum OsError {
  /// There is not enough memory to allocate a heap.
  NotEnoughRam,

  /// Ran out of memory for dynamic allocation.
  OutOfMemory,

  /// A thread has overflowed its allocated stack.
  StackOverflow,

  /// An interrupt or other kernel routine has overflowed its allocated stack.
  KernelStackOverflow,

  /// A kernel memory guard has been corrupted
  KernelGuardCrashed,

  /// It was impossible to schedule a thread for execution, because all
  /// threads have died or are deadlocked.
  NoSchedulableThreads,

  /// Some kind of 'impossible' internal error condition has arisen...
  InternalError,

  /// An attempt to make an illegal thread state change has taken place
  BadThreadState,

  /// The maximum number of threads has been reached
  OutOfThreads,

  /// Call to free a block of memory twice
  DoubleFree,

  /// Bad parameters were passed to a system call
  BadParams,

  /// Calls to inhibit sleep/standby nested too deep
  NestedInhibitTooDeep,

  /// Arithmetic overflow/underflow
  Arithmetic,

  /// A call to the default panic!() handler was made
  Panic,

  /// A call to yield() was made that is not permitted (typically, yield
  /// while interrupts are disabled.)
  CannotYield,

  /// The event queue for Oxide events overflowed
  OxideEventOverflow,

  /// You dropped a supposedly undroppable [`StaticWrap`]
  ///
  /// [`StaticWrap`]: avr_oxide::StaticWrap
  StaticDropped,

  /// You violated the borrowing rules of a [`StaticWrap`]
  ///
  /// [`StaticWrap`]: avr_oxide::StaticWrap
  StaticBorrow,

  /// The application called `panic_if_error!` on an Error
  UnwrapError,

  /// The application called `panic_if_none!` on a None
  UnwrapNone
}

// Code ======================================================================
pub trait OxideTryFrom<T>
where
  T: Sized
{
  type Error;

  fn oxide_try_from(_: T) -> OxideResult<Self, Self::Error> where Self: Sized;
}

impl<T,E> OxideResult<T,E> {
  pub const fn is_ok(&self) -> bool {
    match self {
      OxideResult::Ok(_) => true,
      OxideResult::Err(_) => false
    }
  }
  pub const fn is_err(&self) -> bool {
    match self {
      OxideResult::Ok(_) => false,
      OxideResult::Err(_) => true
    }

  }
  pub fn unwrap(self) -> T {
    match self {
      OxideResult::Ok(t) => t,
      OxideResult::Err(_) => {
        halt(OsError::UnwrapError);
      }
    }

  }
  pub fn expect_err(self) -> E {
    match self {
      OxideResult::Ok(_) => {
        halt(OsError::UnwrapError)
      }
      OxideResult::Err(e) => e
    }

  }
  pub fn unwrap_or(self, default: T) -> T {
    match self {
      OxideResult::Ok(t) => t,
      OxideResult::Err(_) => default
    }

  }
  pub fn unwrap_or_else<F: FnOnce(E)->T>(self, op: F) -> T {
    match self {
      OxideResult::Ok(t) => t,
      OxideResult::Err(e) => { (op)(e) }
    }

  }
}

impl<T,E> Try for OxideResult<T,E> {
  type Output = T;
  type Residual = OxideResult<core::convert::Infallible, E>;

  #[inline]
  fn from_output(output: Self::Output) -> Self {
    OxideResult::Ok(output)
  }

  #[inline]
  fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {
    match self {
      OxideResult::Ok(t) => ControlFlow::Continue(t),
      OxideResult::Err(e) => ControlFlow::Break(OxideResult::Err(e))
    }
  }
}

impl<T, E, F: ~const From<E>> const core::ops::FromResidual<OxideResult<core::convert::Infallible, E>>
    for OxideResult<T, F>
{
  #[inline]
  fn from_residual(residual: OxideResult<core::convert::Infallible, E>) -> Self {
    match residual {
      OxideResult::Err(e) => return OxideResult::Err(From::from(e)),
      OxideResult::Ok(_) => { panic!() }
    }
  }
}

impl<T, E> core::ops::Residual<T> for OxideResult<core::convert::Infallible, E> {
    type TryType = OxideResult<T, E>;
}


impl OsError {
  /// Turn this error into a bit representation of the pulse train we should
  /// send to the debug pin to represent this error.
  fn pulse_train(&self) -> u32 {
    match self {
      Self::NotEnoughRam           => 0b_0000_0000_0000_0000_0000_0000_0000_0001,
      Self::OutOfMemory            => 0b_0000_0000_0000_0000_0000_0000_0000_0101,
      Self::StackOverflow          => 0b_0000_0000_0000_0000_0000_0000_0001_0101,
      Self::KernelStackOverflow    => 0b_0000_0000_0000_0000_0000_0001_0101_0111,
      Self::KernelGuardCrashed     => 0b_0000_0000_0000_0000_0000_0111_0101_0111,
      Self::OxideEventOverflow     => 0b_0000_0000_0000_0000_0001_0101_0111_0111,
      Self::NoSchedulableThreads   => 0b_0000_0000_0000_0000_0000_0000_0101_0101,
      Self::CannotYield            => 0b_0000_0000_0000_0000_0000_0101_0101_0111,
      Self::InternalError          => 0b_0000_0000_0000_0000_0000_0001_0101_0101,
      Self::BadThreadState         => 0b_0000_0000_0000_0000_0000_0101_0111_0101,
      Self::OutOfThreads           => 0b_0000_0000_0000_0000_0000_0101_0101_0101,
      Self::DoubleFree             => 0b_0000_0000_0000_0000_0001_0101_0101_0101,
      Self::BadParams              => 0b_0000_0000_0000_0000_0000_0000_0001_1101,
      Self::NestedInhibitTooDeep   => 0b_0000_0000_0000_0001_0101_0101_0101_0101,
      Self::Arithmetic             => 0b_0000_0000_0000_0000_0000_0001_1101_1101,
      Self::Panic                  => 0b_0000_0000_0000_0000_0001_1101_1101_1101,
      Self::StaticDropped          => 0b_0000_0000_0000_0000_0000_0000_0111_0101,
      Self::StaticBorrow           => 0b_0000_0000_0000_0000_0000_0001_0111_0101,
      Self::UnwrapError            => 0b_0000_0000_0000_0000_0101_1101_1101_1101,
      Self::UnwrapNone             => 0b_0000_0000_0000_0001_0101_1101_1101_1101,
    }
  }
}

/**
 * Set the pin which will be used when an OS error occurs to transmit the
 * error code.
 *
 * ### Default pin for Arduino
 * If avr_oxide is built with the `arduino` feature, the default pin
 * corresponding to the Arduino's debug LED will already be set, and it is
 * not necessary to call this function.  (It is still permissible to do so
 * to use an alternative pin if desired, however.)
 */
pub fn set_debug_pin(pin: &'static dyn Pin) {
  avr_oxide::concurrency::interrupt::isolated(|_isotoken|{
    unsafe {
      core::ptr::replace(&mut DEBUGPIN, Some(pin));
    }
  })
}

/**
 * Halt execution with the given OS error.
 *
 * The pulse code for the given error will be continuously sent on the debugging
 * pin previously set using the `set_debug_pin` method.
 *
 * ### Hardware Debuggers
 * A `break` instruction is included between pulse trains, meaning a device
 * with a hardware debugger/ICE attached should drop to the debugger.
 */
#[cfg(target_arch="avr")]
pub fn halt(error: OsError) -> ! {
  unsafe {
    avr_oxide::concurrency::interrupt::disable_interrupts();

    #[cfg(feature="panicout")]
    {
      debug::print("\r\n\nHALT: ");
      debug::print_u32(error.pulse_train());
      debug::print("\r\n");
      debug::print_thread_state();
    }

    // We want the flash-rate to be *roughly* invariant to the clock-speed and
    // powersaving mode.  But only roughly :-).
    const DELAYLOOP : u32 = (avr_oxide::deviceconsts::clock::MASTER_CLOCK_HZ / 100) / avr_oxide::deviceconsts::clock::MASTER_CLOCK_PRESCALER as u32;

    match DEBUGPIN {
      Some(pin) => {
        let pulse_code = error.pulse_train();

        // Set up the pin as an output
        pin.set_mode(PinMode::Output);
        pin.set_interrupt_mode(InterruptMode::Disabled);
        pin.set_low();

        loop {
          let mut mask : u32 = 1u32;

          for _i in 0..32 {
            if pulse_code & mask > 0 {
              pin.set_high();
            } else {
              pin.set_low();
            }

            mask <<= 1;

            for _d in 0..DELAYLOOP {
              core::arch::asm!("nop");
            }
          }
          // If there is a debugger attached, it'd be nice to break
          core::arch::asm!("break");
        }
      },
      None => {
        // No pin to toggle; just go into a busy loop (sleeping as much
        // as possible for want of battery life.  If there is a debugger
        // attached, we'll fall into it on the Break.
        core::arch::asm!(
        "break",
        "0: ", "sleep",
        "jmp 0b",
        options(noreturn)
        );
      }
    }
  }
}

#[cfg(not(target_arch="avr"))]
pub fn halt(error: OsError) -> ! {
  panic!("OS Error: {:?}", error);
}


/**
 * A macro you can use as a substitute for .unwrap() on Options that will simply
 * panic if the result is None.  You will want this to avoid hauling in all of
 * the garbage associated with the Debug and Formatter types that comes
 * along with the 'real' unwrap() implementation.  With small EEPROMs/Flash
 * we don't have the room for that luxury.
 */
#[macro_export]
macro_rules! panic_if_none {
  ($result:expr) => {
    match $result {
      Some(value) => value,
      None => { avr_oxide::oserror::halt(avr_oxide::oserror::OsError::UnwrapNone); }
    }
  };
  ($result:expr,$paniccode:expr) => {
    match $result {
      Some(value) => value,
      None => { avr_oxide::oserror::halt($paniccode); }
    }
  };

}

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