avr-oxide 0.3.1

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;

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

#[derive(Copy,Clone,PartialEq,Debug)]
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,
}

// Code ======================================================================
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,
    }
  }
}

/**
 * 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(crate) fn halt(error: OsError) -> ! {
  panic!("OS Error: {:?}", error);
}


#[macro_export]
macro_rules! halt_if_err {
  ($result:expr, $error:expr) => {
    match $result {
      Ok(value) => value,
      Err(_) => avr_oxide::oserror::halt($error)
    }
  }
}

#[macro_export]
macro_rules! halt_if_none {
  ($result:expr, $error:expr) => {
    match $result {
      Some(value) => value,
      None => avr_oxide::oserror::halt($error)
    }
  }
}

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