avr-oxide 0.4.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::{ OxideLed, OxideButton, OxideSerialBus };
//! # use avr_oxide::hal::generic::twi::{TwoWireMaster, TwiAddr, InterfaceMode, PortSpeed, RetryStrategy, Command, CommandBuilder };
//! # use avr_oxide::io::Write;
//! # use avr_oxide::boards::board;
//! # use avr_oxide::StaticWrap;
//! 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 = StaticWrap::new(OxideSerialBus::using_bus(hardware::twi::twi0::instance().mux(hardware::twi::twi0::TwiPins::MasterASlaveC).mode(InterfaceMode::I2C, PortSpeed::Fast)));
//!   supervisor.listen(bus.borrow());
//!   let mut i2c_device = OxideSerialBus::client(bus.borrow(), 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::event::{EventSink, EventSource, OxideEvent};
use avr_oxide::hal::generic::twi::{Command, CommandBuilder, TwiAddr, TwiCommandCompleteCallback, TwiError, TwoWireMaster};
use avr_oxide::{AsStaticRef, OxideResult, panic_if_none};
use avr_oxide::private::binaryringq::BinaryRingQ;
use avr_oxide::sync::Mutex;
use avr_oxide::OxideResult::{Ok,Err};

// Declarations ==============================================================
pub trait SerialBusClient {
  fn get_bus_addr(&self) -> TwiAddr;
  fn clone_with_bus_addr(&self, addr: TwiAddr) -> Self;

  fn write_from(&mut self, buffer: &[u8]) -> OxideResult<(),TwiError>;
  fn write_from_multiple(&mut self, buffers: &[&[u8]]) -> OxideResult<(),TwiError>;
  fn read_into(&mut self, buffer: &mut [u8]) -> OxideResult<usize,TwiError>;
  fn write_then_read(&mut self, command: &[u8], result: &mut [u8]) -> OxideResult<usize,TwiError>;
  fn write_multiple_then_read(&mut self, commands: &[&[u8]], result: &mut [u8]) -> OxideResult<usize,TwiError>;
}

pub trait UsesSerialBusClient<BC>
where
  BC: SerialBusClient
{
  fn using_client(client: BC) -> Self;
}

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

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

pub struct SerialBusClientImpl<'sbc,S>
where
  S: EventSink
{
  builder: CommandBuilder,
  master: &'sbc SerialBus<S>
}

// Code ======================================================================

impl<S> SerialBus<S>
where
  S: EventSink
{
  /// Create an instance using the given TWI bus device.  You should
  /// perform any steps required to configure the bus (e.g. setting the
  /// bus mode) beforehand.
  pub fn using_bus(bus: &'static mut dyn TwoWireMaster) -> Self {
    SerialBus {
      bus: Mutex::new(bus),
      phantom: PhantomData::default(),
      response: UnsafeCell::new(None),
      waiting_response: UnsafeCell::new(BinaryRingQ::new())
    }
  }

  /// Instantiate a client for the given TWI device address.
  pub fn client<M: AsStaticRef<Self>>(master: M, address: TwiAddr) -> SerialBusClientImpl<'static,S> {
    unsafe {
      SerialBusClientImpl {
        builder: CommandBuilder::new(address),
        master: master.as_static_ref()
      }
    }
  }

  fn command_complete(&mut self, isotoken: Isolated, result: OxideResult<(Command<'static>, 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<'static>) -> OxideResult<(Command<'static>, 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();
      panic_if_none!(response.take(), avr_oxide::oserror::OsError::InternalError)
    }
  }
}

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

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

impl<'sbc,S> SerialBusClient for SerialBusClientImpl<'sbc,S>
where
  S: EventSink
{
  fn get_bus_addr(&self) -> TwiAddr {
    self.builder.get_address()
  }

  fn clone_with_bus_addr(&self, addr: TwiAddr) -> Self {
    Self {
      builder: self.builder.clone().address(addr),
      master: self.master
    }
  }

  fn write_from(&mut self, buffer: &[u8]) -> OxideResult<(),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 buffer : &'static[u8] = core::mem::transmute(buffer);

      let command = self.builder.write_cmd(buffer);

      self.master.command_blocking(command)?;
      Ok(())
    }
  }

  fn write_from_multiple(&mut self, buffers: &[&[u8]]) -> OxideResult<(),TwiError> {
    unsafe {
      // See note above re. safety
      let buffers : &'static[&'static [u8]] = core::mem::transmute(buffers);

      let command = self.builder.write_multiple_cmd(buffers);

      self.master.command_blocking(command)?;
      Ok(())
    }
  }

  fn read_into(&mut self, buffer: &mut [u8]) -> OxideResult<usize,TwiError>{
    unsafe {
      // See note above re. safety
      let buffer : &'static mut [u8] = core::mem::transmute(buffer);

      let command = self.builder.read_cmd(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]) -> OxideResult<usize,TwiError> {
    unsafe {
      // See note above re. safety
      let c_buffer : &'static[u8] = core::mem::transmute(command);
      let r_buffer : &'static mut [u8] = core::mem::transmute(result);

      let command = self.builder.wtr_cmd(c_buffer, r_buffer);

      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]) -> OxideResult<usize, TwiError> {
    unsafe {
      // See note above re. safety
      let c_buffers : &'static[&'static [u8]] = core::mem::transmute(commands);
      let r_buffer : &'static mut [u8] = core::mem::transmute(result);

      let command = self.builder.wmtr_cmd(c_buffers, r_buffer);

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

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