avr-oxide 0.2.2

An extremely simple Rusty operating system for AVR microcontrollers
/* eeprom.rs
 *
 * Developed by Tim Walls <tim.walls@snowgoons.com>
 * Copyright (c) All Rights Reserved, Tim Walls
 */
//! Low-level access to the AVR's EEPROM module.
//!
//! This device can be used directly by an application program to access
//! the EEPROM space(s) provided by the microcontroller.  It exposes
//! simple byte-copy methods to write to arbitrary locations within the
//! EEPROM space, as well as access to Reader and Writer proxies for use
//! with the Persistent trait.
//!
//! # Usage
//! ```rust
//! # use oxide_macros::Persist;
//! # use avr_oxide::util::persist::Persist;
//! # use avr_oxide::hal::atmega4809::hardware;
//! # use avr_oxide::hal::generic::eeprom::EepromSpace;
//! # use avr_oxide::panic_if_err;
//! #
//! #[derive(Persist)]
//! struct MyDataStructure {
//!    number: u8,
//!    vector: Vec<u16>,
//! }
//!
//! pub fn load_save_object() -> ! {
//!   let hardware = hardware::instance();
//!   let eeprom = &mut hardware.eeprom;
//!
//!   let mut thing : MyDataStructure = panic_if_err!(Persist::load_with(eeprom.reader()));
//!   thing.number += 1u8;
//!   panic_if_err!(thing.save_with(eeprom.writer()));
//!
//! #   loop{}
//! }
//! ```


// Imports ===================================================================
#[cfg(target_arch="avr")]
use avr_oxide::io::{Read, Write};
#[cfg(not(target_arch="avr"))]
use std::io::{Read, Write};

// Declarations ==============================================================
pub trait EepromSpace<'e> {
  const SIZE : usize;
  type W: Write;
  type R: Read;

  /// Read from the EEPROM space, from the given offset, into the buffer
  /// provided.  The entire buffer will be filled (up to a maximum of
  /// Self::SIZE bytes.)
  ///
  /// # Returns
  /// Number of bytes read
  fn read_at_into(&self, offset: usize, dest: &mut [u8]) -> usize;

  /// Write into the EEPROM space from the given buffer.  The entire buffer
  /// will be written starting at the given offset address.
  ///
  /// # Returns
  /// Number of bytes written
  ///
  /// # Panics
  /// Panics if the given (buffer+offset) is outside the EEPROM area.
  fn write_at_from(&mut self, offset: usize, src: &[u8]) -> usize;

  /// Return a Writer that will write to the EEPROM space
  fn writer(&'e mut self) -> Self::W;

  /// Return a Writer that will write to the EEPROM space starting at the
  /// given byte
  fn writer_at(&'e mut self, offset: usize) -> Self::W;

  /// Return a Reader that will read from the EEPROM space
  fn reader(&'e self) -> Self::R;

  /// Return a Reader that will read from the EEPROM space starting at the
  /// given byte
  fn reader_at(&'e self, offset: usize) -> Self::R;
}

// Code ======================================================================
#[cfg(target_arch="avr")]
pub mod base {
  pub mod nvmctrl {
    use avr_oxide::devices::internal::StaticShareable;
    use avr_oxide::hal::generic::eeprom::EepromSpace;
    use avr_oxide::{v_read, v_write};
    use avr_oxide::hal::generic::cpu::base::AvrCpuControlBlock;
    use avr_oxide::hal::generic::cpu::{ConfigurationChange, Cpu};
    use avr_oxide::io::{Read, Write};

    #[repr(C)]
    pub struct AtmelNvmCtrl {
      pub(crate) ctrla: u8,
      pub(crate) ctrlb: u8,
      pub(crate) status: u8,
      pub(crate) intctrl: u8,
      pub(crate) intflags: u8,
      pub(crate) reserved: u8,
      pub(crate) datah: u8,
      pub(crate) datal: u8,
      pub(crate) addrh: u8,
      pub(crate) addrl: u8
    }

    pub struct EepromNvmCtrlImpl<const SIZE: usize, const PAGESIZE: usize, const PAGES: usize> {

      pub(crate) control: &'static mut AtmelNvmCtrl,
      pub(crate) buffer:  &'static mut [u8; SIZE],
      pub(crate) cpu:     &'static mut AvrCpuControlBlock,
    }

    pub struct EepromNvmCtrlReader<'r,const SIZE: usize, const PAGESIZE: usize, const PAGES: usize> {
      eeprom: &'r EepromNvmCtrlImpl<SIZE,PAGESIZE,PAGES>,
      cursor: usize
    }

    pub struct EepromNvmCtrlWriter<'w,const SIZE: usize, const PAGESIZE: usize, const PAGES: usize> {
      eeprom: &'w mut EepromNvmCtrlImpl<SIZE,PAGESIZE,PAGES>,
      cursor: usize
    }

    impl<'e,const SIZE: usize, const PAGESIZE: usize, const PAGES: usize> StaticShareable for EepromNvmCtrlImpl<SIZE,PAGESIZE,PAGES> {}

    impl<'e,const SIZE: usize, const PAGESIZE: usize, const PAGES: usize> EepromSpace<'e> for EepromNvmCtrlImpl<SIZE,PAGESIZE,PAGES> {
      const SIZE: usize = SIZE;
      type W = EepromNvmCtrlWriter<'e,SIZE,PAGESIZE,PAGES>;
      type R = EepromNvmCtrlReader<'e,SIZE,PAGESIZE,PAGES>;

      fn read_at_into(&self, offset: usize, dest: &mut [u8]) -> usize {
        let mut bytes_read : usize = 0;

        for idx in 0..dest.len() {
          let loc = offset+idx;

          if loc >= SIZE {
            break;
          } else {
            dest[idx] = unsafe { v_read!(u8, self.buffer[loc]) };
            bytes_read += 1;
          }
        }

        bytes_read
      }

      fn write_at_from(&mut self, offset: usize, src: &[u8]) -> usize {
        let mut bytes_consumed : usize = 0;

        // Copy down backwards so we can find the page boundaries neatly
        let mut page_dirty = false;
        for idx in (0..src.len()).rev() {
          let loc = offset+idx;

          if loc >= SIZE { // Don't do this, please
            panic!()
          }

          // Read the given byte from the page buffer.  This will also force
          // the chip to wait until any pending writes are complete, so we
          // don't need to check the status separately.
          let current_val = unsafe { v_read!(u8, self.buffer[loc]) };
          if current_val != src[idx] {
            unsafe { v_write!(u8, self.buffer[loc], src[idx]) };
            page_dirty = true;
          }
          bytes_consumed += 1;

          if ((loc % PAGESIZE) == 0) || (idx==0) { // Reached the end of a page or last byte
            if page_dirty {
              // Instruct the chip to write the page
              unsafe {
                self.cpu.write_protected(ConfigurationChange::SelfProgramming,
                                         &mut self.control.ctrla, 0x03); // 0x03 == ERWP (Erase and Write Page)
              }
            }
            page_dirty = false; // We movin' on to the next page now
          }
        }

        bytes_consumed
      }

      fn writer(&'e mut self) -> Self::W {
        self.writer_at(0)
      }

      fn writer_at(&'e mut self, offset: usize) -> Self::W {
        EepromNvmCtrlWriter {
          eeprom: self,
          cursor: offset
        }
      }

      fn reader(&'e self) -> Self::R {
        self.reader_at(0)
      }

      fn reader_at(&'e self, offset: usize) -> Self::R {
        EepromNvmCtrlReader {
          eeprom: self,
          cursor: offset
        }
      }
    }

    impl<const SIZE: usize, const PAGESIZE: usize, const PAGES: usize> Read for EepromNvmCtrlReader<'_,SIZE,PAGESIZE,PAGES> {
      fn read(&mut self, buf: &mut [u8]) -> avr_oxide::io::Result<usize> {
        let bytes = self.eeprom.read_at_into(self.cursor, buf);
        self.cursor += bytes;

        match bytes {
          0 => Err(avr_oxide::io::IoError::EndOfFile),
          v => Ok(v)
        }
      }
    }

    impl<const SIZE: usize, const PAGESIZE: usize, const PAGES: usize> Write for EepromNvmCtrlWriter<'_,SIZE,PAGESIZE,PAGES> {
      fn write(&mut self, buf: &[u8]) -> avr_oxide::io::Result<usize> {

        let bytes = self.eeprom.write_at_from(self.cursor, buf);
        self.cursor += bytes;

        match bytes {
          0 => Err(avr_oxide::io::IoError::EndOfFile),
          v => Ok(v)
        }
      }

      fn flush(&mut self) -> avr_oxide::io::Result<()> {
        Ok(())
      }
    }

    #[doc(hidden)]
    #[macro_export]
    macro_rules! atmel_nvmctrl_eeprom_tpl {
      ($nvmctrlref:expr, $pagebufferref:expr, $pagesize:expr, $pages:expr, $cpu:expr) => {
        use avr_oxide::hal::generic::eeprom::base::nvmctrl::EepromNvmCtrlImpl;
        use avr_oxide::mut_singleton;

        pub type EepromImpl = EepromNvmCtrlImpl<{$pagesize*$pages},{$pagesize},{$pages}>;

        mut_singleton!(
          EepromImpl,
          INSTANCE,
          instance,
          EepromNvmCtrlImpl {
            control: core::mem::transmute($nvmctrlref),
            buffer:  core::mem::transmute($pagebufferref),
            cpu:     $cpu
          }
        );
      }
    }
  }

}


#[cfg(not(target_arch="avr"))]
pub mod base {
  use avr_oxide::hal::generic::eeprom::EepromSpace;

  pub struct DummyEepromImpl<const SIZE: usize> {
    pub(crate) buffer: [u8; SIZE]
  }

  impl<'e,const SIZE: usize> EepromSpace<'e> for DummyEepromImpl<SIZE> {
    const SIZE: usize = SIZE;
    type W = std::io::Stderr;
    type R = std::io::Stdin;

    fn read_at_into(&self, offset: usize, dest: &mut [u8]) -> usize {
      todo!()
    }

    fn write_at_from(&mut self, offset: usize, src: &[u8]) -> usize {
      todo!()
    }

    fn writer(&'e mut self) -> Self::W {
      todo!()
    }

    fn writer_at(&'e mut self, offset: usize) -> Self::W {
      todo!()
    }

    fn reader(&'e self) -> Self::R {
      todo!()
    }

    fn reader_at(&'e self, offset: usize) -> Self::R {
      todo!()
    }
  }

  #[doc(hidden)]
  #[macro_export]
  macro_rules! atmel_nvmctrl_eeprom_tpl {
    ($nvmctrlref:expr, $pagebufferref:expr, $pagesize:expr, $pages:expr, $cpu:expr) => {
      use avr_oxide::hal::generic::eeprom::base::DummyEepromImpl;
      use avr_oxide::mut_singleton;

      pub type EepromImpl = DummyEepromImpl<{$pagesize*$pages}>;

      mut_singleton!(
        EepromImpl,
        INSTANCE,
        instance,
        DummyEepromImpl {
          buffer:  [ 0xff; {$pagesize*$pages} ]
        }
      );
    }
  }
}

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