use crate::save::utils::Timeout;
use crate::sync::{Mutex, RawMutexGuard};
use crate::timer::Timer;
use core::ops::Range;
mod asm_utils;
mod eeprom;
mod flash;
mod sram;
mod utils;
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
#[non_exhaustive]
pub enum MediaType {
Sram32K,
Eeprom8K,
Eeprom512B,
Flash64K,
Flash128K,
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum Error {
NoMedia,
WriteError,
OperationTimedOut,
OutOfBounds,
MediaInUse,
IncompatibleCommand,
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct MediaInfo {
pub media_type: MediaType,
pub sector_shift: usize,
pub sector_count: usize,
pub uses_prepare_write: bool,
}
impl MediaInfo {
#[must_use]
pub fn sector_size(&self) -> usize {
1 << self.sector_shift
}
#[must_use]
#[allow(clippy::len_without_is_empty)] pub fn len(&self) -> usize {
self.sector_count << self.sector_shift
}
}
trait RawSaveAccess: Sync {
fn info(&self) -> Result<&'static MediaInfo, Error>;
fn read(&self, offset: usize, buffer: &mut [u8], timeout: &mut Timeout) -> Result<(), Error>;
fn verify(&self, offset: usize, buffer: &[u8], timeout: &mut Timeout) -> Result<bool, Error>;
fn prepare_write(
&self,
sector: usize,
count: usize,
timeout: &mut Timeout,
) -> Result<(), Error>;
fn write(&self, offset: usize, buffer: &[u8], timeout: &mut Timeout) -> Result<(), Error>;
}
static CURRENT_SAVE_ACCESS: Mutex<Option<&'static dyn RawSaveAccess>> = Mutex::new(None);
fn set_save_implementation(access_impl: &'static dyn RawSaveAccess) {
let mut access = CURRENT_SAVE_ACCESS.lock();
assert!(
access.is_none(),
"Cannot initialize the save media engine more than once."
);
*access = Some(access_impl);
}
fn get_save_implementation() -> Option<&'static dyn RawSaveAccess> {
*CURRENT_SAVE_ACCESS.lock()
}
pub struct SaveData {
_lock: RawMutexGuard<'static>,
access: &'static dyn RawSaveAccess,
info: &'static MediaInfo,
timeout: utils::Timeout,
}
impl SaveData {
fn new(timer: Option<Timer>) -> Result<SaveData, Error> {
match get_save_implementation() {
Some(access) => Ok(SaveData {
_lock: utils::lock_media_access()?,
access,
info: access.info()?,
timeout: utils::Timeout::new(timer),
}),
None => Err(Error::NoMedia),
}
}
#[must_use]
pub fn media_info(&self) -> &'static MediaInfo {
self.info
}
#[must_use]
pub fn media_type(&self) -> MediaType {
self.info.media_type
}
#[must_use]
pub fn sector_size(&self) -> usize {
self.info.sector_size()
}
#[must_use]
#[allow(clippy::len_without_is_empty)] pub fn len(&self) -> usize {
self.info.len()
}
fn check_bounds(&self, range: Range<usize>) -> Result<(), Error> {
if range.start >= self.len() || range.end > self.len() {
Err(Error::OutOfBounds)
} else {
Ok(())
}
}
fn check_bounds_len(&self, offset: usize, len: usize) -> Result<(), Error> {
self.check_bounds(offset..(offset + len))
}
pub fn read(&mut self, offset: usize, buffer: &mut [u8]) -> Result<(), Error> {
self.check_bounds_len(offset, buffer.len())?;
self.access.read(offset, buffer, &mut self.timeout)
}
pub fn verify(&mut self, offset: usize, buffer: &[u8]) -> Result<bool, Error> {
self.check_bounds_len(offset, buffer.len())?;
self.access.verify(offset, buffer, &mut self.timeout)
}
#[must_use]
pub fn align_range(&self, range: Range<usize>) -> Range<usize> {
let shift = self.info.sector_shift;
let mask = (1 << shift) - 1;
(range.start & !mask)..((range.end + mask) & !mask)
}
pub fn prepare_write(&mut self, range: Range<usize>) -> Result<SavePreparedBlock, Error> {
self.check_bounds(range.clone())?;
if self.info.uses_prepare_write {
let range = self.align_range(range.clone());
let shift = self.info.sector_shift;
self.access.prepare_write(
range.start >> shift,
range.len() >> shift,
&mut self.timeout,
)?;
}
Ok(SavePreparedBlock {
parent: self,
range,
})
}
}
pub struct SavePreparedBlock<'a> {
parent: &'a mut SaveData,
range: Range<usize>,
}
impl<'a> SavePreparedBlock<'a> {
pub fn write(&mut self, offset: usize, buffer: &[u8]) -> Result<(), Error> {
if buffer.is_empty() {
Ok(())
} else if !self.range.contains(&offset)
|| !self.range.contains(&(offset + buffer.len() - 1))
{
Err(Error::OutOfBounds)
} else {
self.parent
.access
.write(offset, buffer, &mut self.parent.timeout)
}
}
pub fn write_and_verify(&mut self, offset: usize, buffer: &[u8]) -> Result<(), Error> {
self.write(offset, buffer)?;
if !self.parent.verify(offset, buffer)? {
Err(Error::WriteError)
} else {
Ok(())
}
}
}
mod marker {
#[repr(align(4))]
struct Align<T>(T);
static EEPROM: Align<[u8; 12]> = Align(*b"EEPROM_Vnnn\0");
static SRAM: Align<[u8; 12]> = Align(*b"SRAM_Vnnn\0\0\0");
static FLASH512K: Align<[u8; 16]> = Align(*b"FLASH512_Vnnn\0\0\0");
static FLASH1M: Align<[u8; 16]> = Align(*b"FLASH1M_Vnnn\0\0\0\0");
#[inline(always)]
pub fn emit_eeprom_marker() {
crate::sync::memory_read_hint(&EEPROM);
}
#[inline(always)]
pub fn emit_sram_marker() {
crate::sync::memory_read_hint(&SRAM);
}
#[inline(always)]
pub fn emit_flash_512k_marker() {
crate::sync::memory_read_hint(&FLASH512K);
}
#[inline(always)]
pub fn emit_flash_1m_marker() {
crate::sync::memory_read_hint(&FLASH1M);
}
}
#[non_exhaustive]
pub struct SaveManager {}
impl SaveManager {
pub(crate) const fn new() -> Self {
SaveManager {}
}
pub fn init_sram(&mut self) {
marker::emit_sram_marker();
set_save_implementation(&sram::BatteryBackedAccess);
}
pub fn init_flash_64k(&mut self) {
marker::emit_flash_512k_marker();
set_save_implementation(&flash::FlashAccess);
}
pub fn init_flash_128k(&mut self) {
marker::emit_flash_1m_marker();
set_save_implementation(&flash::FlashAccess);
}
pub fn init_eeprom_512b(&mut self) {
marker::emit_eeprom_marker();
set_save_implementation(&eeprom::Eeprom512B);
}
pub fn init_eeprom_8k(&mut self) {
marker::emit_eeprom_marker();
set_save_implementation(&eeprom::Eeprom8K);
}
pub fn access(&mut self) -> Result<SaveData, Error> {
SaveData::new(None)
}
pub fn access_with_timer(&mut self, timer: Timer) -> Result<SaveData, Error> {
SaveData::new(Some(timer))
}
}