/* 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 =====================================================================