avr-oxide 0.3.1

An extremely simple Rusty operating system for AVR microcontrollers
/* serialbus.rs
 *
 * Developed by Tim Walls <tim.walls@snowgoons.com>
 * Copyright (c) All Rights Reserved, Tim Walls
 */
//! Exposes the underlying TWI device interface in a userland-safe way.
//!
//! This is essentially a thread-safe wrapper around the underlying TWI
//! HAL.  To send commands to the bus, you need to get a `SerialBusClient`
//! instance using the [`SerialBus::client()`] method.  Once you have
//! a client, you can issue commands using the various methods provided
//! by [`SerialBusClient`].
//!
//! Only one instance of [`SerialBus`] can exist per physical device, but there
//! may be multiple instances of [`SerialBusClient`] for different client devices.
//!
//! # Usage
//! ```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, OxideSerialBus };
//! # use avr_oxide::hal::generic::twi::{TwoWireMaster, TwiAddr, InterfaceMode, PortSpeed, RetryStrategy, Command};
//! # use avr_oxide::io::Write;
//! # use avr_oxide::boards::board;
//! use avr_oxide::hardware;
//! use avr_oxide::devices::serialbus::SerialBusClient;
//!
//! #[avr_oxide::main(chip="atmega4809")]
//! pub fn main() {
//!   let supervisor = avr_oxide::oxide::instance();
//!   let bus = Handle::new(OxideSerialBus::using_bus(hardware::twi::twi0::instance().mux(hardware::twi::twi0::TwiPins::MasterASlaveC).mode(InterfaceMode::I2C, PortSpeed::Fast)));
//!   supervisor.listen_handle(bus);
//!   let mut i2c_device = bus.client(TwiAddr::addr(0xa0));
//!
//!   static COMMAND_BUFFER: [u8;6] = [0x00u8,0x00u8,0xdeu8,0xadu8,0xbeu8,0xefu8];
//!   i2c_device.write_from(&COMMAND_BUFFER);
//!
//! #    supervisor.run();
//! # }
//! ```
//!
//! [`SerialBus::client()`]: SerialBus::client
//! [`SerialBusClient`]: SerialBusClient
//! [`SerialBus`]: SerialBus

// Imports ===================================================================
use core::cell::UnsafeCell;
use core::marker::PhantomData;
use avr_oxide::concurrency::Isolated;
use avr_oxide::devices::internal::StaticShareable;
use avr_oxide::event::{EventSink, EventSource, OxideEvent};
use avr_oxide::hal::generic::twi::{Command, RetryStrategy, TwiAddr, TwiCommandCompleteCallback, TwiError, TwoWireMaster};
use avr_oxide::halt_if_none;
use avr_oxide::private::binaryringq::BinaryRingQ;
use avr_oxide::sync::Mutex;
use avr_oxide::util::{OwnOrBorrow, OwnOrBorrowMut};

// Declarations ==============================================================
pub trait SerialBusClient {
  fn write_from(&mut self, buffer: &[u8]) -> Result<(),TwiError>;
  fn write_from_multiple(&mut self, buffers: &[&[u8]]) -> Result<(),TwiError>;
  fn read_into(&mut self, buffer: &mut [u8]) -> Result<usize,TwiError>;
  fn write_then_read(&mut self, command: &[u8], result: &mut [u8]) -> Result<usize,TwiError>;
  fn write_multiple_then_read(&mut self, commands: &[&[u8]], result: &mut [u8]) -> Result<usize,TwiError>;
}

pub struct SerialBus<B,S>
where
  B: 'static + TwoWireMaster,
  S: EventSink
{
  bus: Mutex<&'static mut B>,
  phantom: PhantomData<S>,

  response: UnsafeCell<Option<Result<(Command,usize),TwiError>>>,
  waiting_response: UnsafeCell<BinaryRingQ> // We're using the BRQ as a semaphore
}

impl<B,S> StaticShareable for SerialBus<B,S>
where
  B: 'static + TwoWireMaster,
  S: EventSink
{}

pub struct SerialBusClientImpl<'sbc,B,S>
where
  B: 'static + TwoWireMaster,
  S: EventSink
{
  address: TwiAddr,
  master: &'sbc SerialBus<B,S>
}

// Code ======================================================================
impl<B,S> SerialBus<B,S>
where
  B: 'static + TwoWireMaster,
  S: EventSink
{
  /// Create an instance using the given TWI bus device.  You should
  /// call the [`TwoWireMaster::mode()`] method to configure the
  /// bus mode beforehand.
  ///
  /// [`TwoWireMaster::mode()`]: avr_oxide::hal::generic::twi::TwoWireMaster::mode
  pub fn using_bus(bus: &'static mut B) -> Self {
    SerialBus {
      bus: Mutex::new(bus),
      phantom: PhantomData::default(),
      response: UnsafeCell::new(None),
      waiting_response: UnsafeCell::new(BinaryRingQ::new())
    }
  }

  /// Get a client for the given TWI device address.
  pub fn client(&self, address: TwiAddr) -> SerialBusClientImpl<B,S> {
    SerialBusClientImpl {
      address,
      master: self
    }
  }

  fn command_complete(&mut self, isotoken: Isolated, result: Result<(Command,usize),TwiError>){
    self.response.get_mut().replace(result);

    unsafe {
      let waiting_response = &mut *self.waiting_response.get();
      let _ = waiting_response.append(isotoken, true);
    }
  }

  fn command_blocking(&self, command: Command) -> Result<(Command,usize),TwiError> {
    let mut bus = self.bus.lock();

    // Ask the bus to send the command
    bus.command(command);

    // Wait until we are signalled with a response
    unsafe {
      let waiting_response = &mut *self.waiting_response.get();
      waiting_response.consume_blocking();

      let response = &mut *self.response.get();
      halt_if_none!(response.take(), avr_oxide::oserror::OsError::InternalError)
    }
  }
}

impl<B,S> EventSource for SerialBus<B,S>
where
  B: 'static + TwoWireMaster,
  S: EventSink
{
  fn listen(&'static self) {
    let bus = self.bus.lock();
    bus.set_command_complete_callback(TwiCommandCompleteCallback::WithData(|isotoken, _src, result, udata|{
      let myself = halt_if_none!(udata, avr_oxide::oserror::OsError::InternalError) as *mut SerialBus<B,S>;
      unsafe {
        (*myself).command_complete(isotoken, result);
      }
    }, self as *const dyn core::any::Any));
  }

  fn process_event(&self, _evt: OxideEvent) {
    todo!()
  }
}

impl<'sbc,B,S> SerialBusClient for SerialBusClientImpl<'sbc,B,S>
where
  B: 'static + TwoWireMaster,
  S: EventSink
{
  fn write_from(&mut self, buffer: &[u8]) -> Result<(),TwiError>{
    unsafe {
      // Why unsafe?  We have to frig the lifetimes here.  Because our buffer
      // is going into the hands of the kernel/ISRs, it needs to be 'static'.
      // But *I* know that it actually only needs to live until the call to
      // the driver is complete.  So, I use `transmute` here to change
      // the reference lifetime.
      let command = Command::Write(RetryStrategy::UntilAcknowledged,
                                   self.address,
                                   OwnOrBorrow::Borrow(core::mem::transmute(buffer)));

      match self.master.command_blocking(command) {
        Ok(_) => Ok(()),
        Err(e) => Err(e)
      }
    }
  }

  fn write_from_multiple(&mut self, buffers: &[&[u8]]) -> Result<(),TwiError> {
    unsafe {
      // See note above re. safety
      let command = Command::WriteMultiple(RetryStrategy::UntilAcknowledged,
                                   self.address,
                                   core::mem::transmute(buffers));

      match self.master.command_blocking(command) {
        Ok(_) => Ok(()),
        Err(e) => Err(e)
      }
    }
  }

  fn read_into(&mut self, buffer: &mut [u8]) -> Result<usize,TwiError>{
    unsafe {
      // See note above re. safety
      let command = Command::Read(RetryStrategy::UntilAcknowledged,
                                  self.address,
                                  OwnOrBorrowMut::Borrow(core::mem::transmute(buffer)));

      match self.master.command_blocking(command) {
        Ok((_,size)) => Ok(size),
        Err(e) => Err(e)
      }
    }
  }

  fn write_then_read(&mut self, command: &[u8], result: &mut [u8]) -> Result<usize,TwiError> {
    unsafe {
      // See note above re. safety
      let command = Command::WriteThenRead(RetryStrategy::UntilAcknowledged,
                                           self.address,
                                           OwnOrBorrow::Borrow(core::mem::transmute(command)),
                                           OwnOrBorrowMut::Borrow(core::mem::transmute(result)));

      match self.master.command_blocking(command) {
        Ok((_,size)) => Ok(size),
        Err(e) => Err(e)
      }
    }
  }

  fn write_multiple_then_read(&mut self, commands: &[&[u8]], result: &mut [u8]) -> Result<usize, TwiError> {
    unsafe {
      // See note above re. safety
      let command = Command::WriteMultipleThenRead(RetryStrategy::UntilAcknowledged,
                                           self.address,
                                           core::mem::transmute(commands),
                                           OwnOrBorrowMut::Borrow(core::mem::transmute(result)));

      match self.master.command_blocking(command) {
        Ok((_,size)) => Ok(size),
        Err(e) => Err(e)
      }
    }
  }
}

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