avr-oxide 0.3.0

An extremely simple Rusty operating system for AVR microcontrollers
/* oxide.rs
 *
 * Developed by Tim Walls <tim.walls@snowgoons.com>
 * Copyright (c) All Rights Reserved, Tim Walls
 */
//! Simple supervisor implementation for the AVRoxide runtime.
//!
//! The supervisor acts as an event sink for the device drivers' interrupt
//! service routines.  These service routines send events to the supervisor
//! which queues them for processing.
//!
//! The queued events are then dispatched in the *userland* context to
//! trigger the callback routines you provide via the device drivers.
//!
//! # Usage
//! Obtain a reference to the supervisor using the `avr_oxide::oxide::instance()` method.
//!
//! Create your device driver instances and register any callbacks.
//!
//! To receive events from a device driver, you must tell the supervisor
//! to listen to that device with the `listen()` method.
//!
//! Finally, call the `run()` method on the supervisor to enter the main
//! event handling loop (never returns.)
//!
//! ```rust,no_run
//! #![no_std]
//! #![no_main]
//!
//! use avr_oxide::alloc::boxed::Box;
//! use avr_oxide::devices::UsesPin;
//! use avr_oxide::devices::debouncer::Debouncer;
//! use avr_oxide::devices::{ Handle, OxideLed, OxideButton, OxideSerialPort };
//! use avr_oxide::boards::board;
//!
//!
//! #[avr_oxide::main(chip="atmega4809")]
//! pub fn main() {
//!   let supervisor = avr_oxide::oxide::instance();
//!
//!   let mut green_button = Handle::new(OxideButton::using(Debouncer::with_pin(board::pin_a(2))));
//!   green_button.on_click(Box::new(move |_pinid, _state|{
//!     // Do some processing when the button is pressed
//!   }));
//!
//!   // Tell the supervisor which devices to listen to
//!   supervisor.listen_handle(green_button);
//!
//!   // Now enter the event loop
//!   supervisor.run();
//! }
//! ```
//!
//! ## Using a custom pre-event handler
//! You can pass a custom pre-handler closure to the `run_with_prehandler()`
//! method.  This will be executed *before* the default event callback process
//! is dispatched, and returns a `bool` indicating if the default event
//! handling process should still be followed.  If this closure returns `false`,
//! the event will be discarded without the event being passed to the device
//! driver.
//!
//! This can be useful if you are using a Watchdog device as a way to kick
//! the watchdog when any event is processed, regardless of source or
//! destination.
//!
//! ```rust,no_run
//! #![no_std]
//! #![no_main]
//!
//! use avr_oxide::alloc::boxed::Box;
//! use avr_oxide::devices::UsesPin;
//! use avr_oxide::devices::debouncer::Debouncer;
//! use avr_oxide::devices::{ Handle, OxideLed, OxideButton, OxideSerialPort };
//! use avr_oxide::boards::board;
//! use avr_oxide::event::OxideEvent;
//!
//! #[avr_oxide::main(chip="atmega4809")]
//! pub fn main() {
//!   let supervisor = avr_oxide::oxide::instance();
//!
//!   let mut green_button = Handle::new(OxideButton::using(Debouncer::with_pin(board::pin_a(2))));
//!   green_button.on_click(Box::new(move |_pinid, _state|{
//!     // Do some processing when the button is pressed
//!   }));
//!
//!   // Tell the supervisor which devices to listen to
//!   supervisor.listen_handle(green_button);
//!
//!   // Now enter the event loop
//!   supervisor.run_with_prehandler(|event|{
//!     match event {
//!       OxideEvent::Initialise => {
//!         // Do some custom initialisation
//!         true
//!       }
//!       OxideEvent::ClockTick(_timer,_ticks) => {
//!         // Ignore all clocktick events
//!         false
//!       }
//!       _ => {
//!         // Everything else should follow default processing
//!         true
//!       }
//!     }
//!   });
//! }
//! ```


// Imports ===================================================================
use avr_oxide::devices::internal::StaticShareable;
use avr_oxide::devices::Handle;
use avr_oxide::private::ringq::RingQ;
use avr_oxide::event::{OxideEvent, EventSink, EventSource, OxideEventEnvelope};
use avr_oxide::concurrency::Isolated;
use avr_oxide::deviceconsts::oxide;
use core::mem::MaybeUninit;

// Declarations ==============================================================

pub struct OxideSupervisor<'e>
{
  event_q: RingQ<OxideEventEnvelope<'e>, {oxide::EVENT_QUEUE}>,
}

//static mut GLOBAL_INSTANCE : Option<&mut OxideSupervisor> = None;
static mut SUPERVISOR : MaybeUninit<OxideSupervisor> = MaybeUninit::uninit();


// Code ======================================================================
/**
 * Initialise the global supervisor instance
 */
pub(crate) unsafe fn initialise() {
  core::ptr::write(SUPERVISOR.as_mut_ptr(),
                   OxideSupervisor {
                     event_q: RingQ::new_with(OxideEventEnvelope::anon(OxideEvent::Initialise)),
                   });
}

pub fn instance() -> &'static mut OxideSupervisor<'static> {
  unsafe {
    SUPERVISOR.assume_init_mut()
  }
}

impl OxideSupervisor<'_> {
  /**
   * Called to have the supervisor listen for events from this device.
   */
  pub fn listen_handle<EP: 'static + EventSource + StaticShareable>(&mut self, source: Handle<EP>) {
    Handle::static_deref(&source).listen();
  }

  /**
   * Called to have the supervisor listen for events from this device.
   */
  pub fn listen<EP: 'static + EventSource>(&mut self, source: &'static EP) {
    source.listen();
  }

  /**
   * Enter the event loop - and never return (*evil cackle*).  A pre-handler
   * closure is provided that will be called with the event before the
   * default handling method (a callback to the originator) is executed.  If
   * this prehandler returns `false`, then the standard event handling will
   * not be executed for this event and it will be discarded immediately
   * after the prehandler.
   */
  pub fn run_with_prehandler<F: FnMut(OxideEvent) -> bool>(&mut self, mut pre_handler: F) -> ! {
    loop {
      let event = self.event_q.consume_blocking();

      if pre_handler(event.open_event()) {
        event.invoke_recipient();
      }
    }
  }

  /**
   * Enter the event loop and never return.
   */
  pub fn run(&mut self) -> ! {
    self.run_with_prehandler(|_|{true});
  }
}

impl EventSink for OxideSupervisor<'_> {
  fn event(isotoken: Isolated, event: OxideEventEnvelope) {
    let supervisor = instance();

    unsafe {
      if (*supervisor).event_q.append(isotoken, core::mem::transmute(event)).is_err() {
        #[cfg(feature="runtime_checks")]
        avr_oxide::oserror::halt(avr_oxide::oserror::OsError::OxideEventOverflow);
      }
    }
  }
}


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