| // Copyright (c) 2017-2021 Linaro LTD |
| // Copyright (c) 2017-2018 JUUL Labs |
| // |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| //! A flash simulator |
| //! |
| //! This module is capable of simulating the type of NOR flash commonly used in microcontrollers. |
| //! These generally can be written as individual bytes, but must be erased in larger units. |
| |
| mod pdump; |
| |
| use crate::pdump::HexDump; |
| use log::info; |
| use rand::{ |
| self, |
| distributions::Standard, |
| Rng, |
| }; |
| use std::{ |
| collections::HashMap, |
| fs::File, |
| io::{self, Write}, |
| iter::Enumerate, |
| path::Path, |
| slice, |
| }; |
| use thiserror::Error; |
| |
| pub type Result<T> = std::result::Result<T, FlashError>; |
| |
| #[derive(Error, Debug)] |
| pub enum FlashError { |
| #[error("Offset out of bounds: {0}")] |
| OutOfBounds(String), |
| #[error("Invalid write: {0}")] |
| Write(String), |
| #[error("Write failed by chance: {0}")] |
| SimulatedFail(String), |
| #[error("{0}")] |
| Io(#[from] io::Error), |
| } |
| |
| // Transition from error-chain. |
| macro_rules! bail { |
| ($item:expr) => (return Err($item.into());) |
| } |
| |
| pub struct FlashPtr { |
| pub ptr: *mut dyn Flash, |
| } |
| unsafe impl Send for FlashPtr {} |
| |
| pub trait Flash { |
| fn erase(&mut self, offset: usize, len: usize) -> Result<()>; |
| fn write(&mut self, offset: usize, payload: &[u8]) -> Result<()>; |
| fn read(&self, offset: usize, data: &mut [u8]) -> Result<()>; |
| |
| fn add_bad_region(&mut self, offset: usize, len: usize, rate: f32) -> Result<()>; |
| fn reset_bad_regions(&mut self); |
| |
| fn set_verify_writes(&mut self, enable: bool); |
| |
| fn sector_iter(&self) -> SectorIter<'_>; |
| fn device_size(&self) -> usize; |
| |
| fn align(&self) -> usize; |
| fn erased_val(&self) -> u8; |
| } |
| |
| fn ebounds<T: AsRef<str>>(message: T) -> FlashError { |
| FlashError::OutOfBounds(message.as_ref().to_owned()) |
| } |
| |
| #[allow(dead_code)] |
| fn ewrite<T: AsRef<str>>(message: T) -> FlashError { |
| FlashError::Write(message.as_ref().to_owned()) |
| } |
| |
| #[allow(dead_code)] |
| fn esimulatedwrite<T: AsRef<str>>(message: T) -> FlashError { |
| FlashError::SimulatedFail(message.as_ref().to_owned()) |
| } |
| |
| /// An emulated flash device. It is represented as a block of bytes, and a list of the sector |
| /// mappings. |
| #[derive(Clone)] |
| pub struct SimFlash { |
| data: Vec<u8>, |
| write_safe: Vec<bool>, |
| sectors: Vec<usize>, |
| bad_region: Vec<(usize, usize, f32)>, |
| // Alignment required for writes. |
| align: usize, |
| verify_writes: bool, |
| erased_val: u8, |
| } |
| |
| impl SimFlash { |
| /// Given a sector size map, construct a flash device for that. |
| pub fn new(sectors: Vec<usize>, align: usize, erased_val: u8) -> SimFlash { |
| // Verify that the alignment is a positive power of two. |
| assert!(align > 0); |
| assert!(align & (align - 1) == 0); |
| |
| let total = sectors.iter().sum(); |
| SimFlash { |
| data: vec![erased_val; total], |
| write_safe: vec![true; total], |
| sectors, |
| bad_region: Vec::new(), |
| align, |
| verify_writes: true, |
| erased_val, |
| } |
| } |
| |
| #[allow(dead_code)] |
| pub fn dump(&self) { |
| self.data.dump(); |
| } |
| |
| /// Dump this image to the given file. |
| #[allow(dead_code)] |
| pub fn write_file<P: AsRef<Path>>(&self, path: P) -> Result<()> { |
| let mut fd = File::create(path)?; |
| fd.write_all(&self.data)?; |
| Ok(()) |
| } |
| |
| // Scan the sector map, and return the base and offset within a sector for this given byte. |
| // Returns None if the value is outside of the device. |
| fn get_sector(&self, offset: usize) -> Option<(usize, usize)> { |
| let mut offset = offset; |
| for (sector, &size) in self.sectors.iter().enumerate() { |
| if offset < size { |
| return Some((sector, offset)); |
| } |
| offset -= size; |
| } |
| None |
| } |
| |
| } |
| |
| pub type SimMultiFlash = HashMap<u8, SimFlash>; |
| |
| impl Flash for SimFlash { |
| /// The flash drivers tend to erase beyond the bounds of the given range. Instead, we'll be |
| /// strict, and make sure that the passed arguments are exactly at a sector boundary, otherwise |
| /// return an error. |
| fn erase(&mut self, offset: usize, len: usize) -> Result<()> { |
| let (_start, slen) = self.get_sector(offset).ok_or_else(|| ebounds("start"))?; |
| let (end, elen) = self.get_sector(offset + len - 1).ok_or_else(|| ebounds("end"))?; |
| |
| if slen != 0 { |
| bail!(ebounds("offset not at start of sector")); |
| } |
| if elen != self.sectors[end] - 1 { |
| bail!(ebounds("end not at start of sector")); |
| } |
| |
| for x in &mut self.data[offset .. offset + len] { |
| *x = self.erased_val; |
| } |
| |
| for x in &mut self.write_safe[offset .. offset + len] { |
| *x = true; |
| } |
| |
| Ok(()) |
| } |
| |
| /// We restrict to only allowing writes of values that are: |
| /// |
| /// 1. being written to for the first time |
| /// 2. being written to after being erased |
| /// |
| /// This emulates a flash device which starts out erased, with the |
| /// added restriction that repeated writes to the same location |
| /// are disallowed, even if they would be safe to do. |
| fn write(&mut self, offset: usize, payload: &[u8]) -> Result<()> { |
| for &(off, len, rate) in &self.bad_region { |
| if offset >= off && (offset + payload.len()) <= (off + len) { |
| let mut rng = rand::thread_rng(); |
| let samp: f32 = rng.sample(Standard); |
| if samp < rate { |
| bail!(esimulatedwrite( |
| format!("Ignoring write to {:#x}-{:#x}", off, off + len))); |
| } |
| } |
| } |
| |
| if offset + payload.len() > self.data.len() { |
| panic!("Write outside of device"); |
| } |
| |
| // Verify the alignment (which must be a power of two). |
| if offset & (self.align - 1) != 0 { |
| panic!("Misaligned write address"); |
| } |
| |
| if payload.len() & (self.align - 1) != 0 { |
| panic!("Write length not multiple of alignment"); |
| } |
| |
| for (i, x) in &mut self.write_safe[offset .. offset + payload.len()].iter_mut().enumerate() { |
| if self.verify_writes && !(*x) { |
| panic!("Write to unerased location at 0x{:x}", offset + i); |
| } |
| *x = false; |
| } |
| |
| let sub = &mut self.data[offset .. offset + payload.len()]; |
| sub.copy_from_slice(payload); |
| Ok(()) |
| } |
| |
| /// Read is simple. |
| fn read(&self, offset: usize, data: &mut [u8]) -> Result<()> { |
| if offset + data.len() > self.data.len() { |
| bail!(ebounds("Read outside of device")); |
| } |
| |
| let sub = &self.data[offset .. offset + data.len()]; |
| data.copy_from_slice(sub); |
| Ok(()) |
| } |
| |
| /// Adds a new flash bad region. Writes to this area fail with a chance |
| /// given by `rate`. |
| fn add_bad_region(&mut self, offset: usize, len: usize, rate: f32) -> Result<()> { |
| if !(0.0..=1.0).contains(&rate) { |
| bail!(ebounds("Invalid rate")); |
| } |
| |
| info!("Adding new bad region {:#x}-{:#x}", offset, offset + len); |
| self.bad_region.push((offset, len, rate)); |
| |
| Ok(()) |
| } |
| |
| fn reset_bad_regions(&mut self) { |
| self.bad_region.clear(); |
| } |
| |
| fn set_verify_writes(&mut self, enable: bool) { |
| self.verify_writes = enable; |
| } |
| |
| /// An iterator over each sector in the device. |
| fn sector_iter(&self) -> SectorIter<'_> { |
| SectorIter { |
| iter: self.sectors.iter().enumerate(), |
| base: 0, |
| } |
| } |
| |
| fn device_size(&self) -> usize { |
| self.data.len() |
| } |
| |
| fn align(&self) -> usize { |
| self.align |
| } |
| |
| fn erased_val(&self) -> u8 { |
| self.erased_val |
| } |
| } |
| |
| /// It is possible to iterate over the sectors in the device, each element returning this. |
| #[derive(Debug, Clone)] |
| pub struct Sector { |
| /// Which sector is this, starting from 0. |
| pub num: usize, |
| /// The offset, in bytes, of the start of this sector. |
| pub base: usize, |
| /// The length, in bytes, of this sector. |
| pub size: usize, |
| } |
| |
| pub struct SectorIter<'a> { |
| iter: Enumerate<slice::Iter<'a, usize>>, |
| base: usize, |
| } |
| |
| impl<'a> Iterator for SectorIter<'a> { |
| type Item = Sector; |
| |
| fn next(&mut self) -> Option<Sector> { |
| match self.iter.next() { |
| None => None, |
| Some((num, &size)) => { |
| let base = self.base; |
| self.base += size; |
| Some(Sector { |
| num, |
| base, |
| size, |
| }) |
| } |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::{Flash, FlashError, SimFlash, Result, Sector}; |
| |
| #[test] |
| fn test_flash() { |
| for &erased_val in &[0, 0xff] { |
| // NXP-style, uniform sectors. |
| let mut f1 = SimFlash::new(vec![4096usize; 256], 1, erased_val); |
| test_device(&mut f1, erased_val); |
| |
| // STM style, non-uniform sectors. |
| let mut f2 = SimFlash::new(vec![16 * 1024, 16 * 1024, 16 * 1024, 64 * 1024, |
| 128 * 1024, 128 * 1024, 128 * 1024], 1, erased_val); |
| test_device(&mut f2, erased_val); |
| } |
| } |
| |
| fn test_device(flash: &mut dyn Flash, erased_val: u8) { |
| let sectors: Vec<Sector> = flash.sector_iter().collect(); |
| |
| flash.erase(0, sectors[0].size).unwrap(); |
| let flash_size = flash.device_size(); |
| flash.erase(0, flash_size).unwrap(); |
| assert!(flash.erase(0, sectors[0].size - 1).is_bounds()); |
| |
| // Verify that write and erase do something. |
| flash.write(0, &[0x55]).unwrap(); |
| let mut buf = [0xAA; 4]; |
| flash.read(0, &mut buf).unwrap(); |
| assert_eq!(buf, [0x55, erased_val, erased_val, erased_val]); |
| |
| flash.erase(0, sectors[0].size).unwrap(); |
| flash.read(0, &mut buf).unwrap(); |
| assert_eq!(buf, [erased_val; 4]); |
| |
| // Program the first and last byte of each sector, verify that has been done, and then |
| // erase to verify the erase boundaries. |
| for sector in §ors { |
| let byte = [(sector.num & 127) as u8]; |
| flash.write(sector.base, &byte).unwrap(); |
| flash.write(sector.base + sector.size - 1, &byte).unwrap(); |
| } |
| |
| // Verify the above |
| let mut buf = Vec::new(); |
| for sector in §ors { |
| let byte = (sector.num & 127) as u8; |
| buf.resize(sector.size, 0); |
| flash.read(sector.base, &mut buf).unwrap(); |
| assert_eq!(buf.first(), Some(&byte)); |
| assert_eq!(buf.last(), Some(&byte)); |
| assert!(buf[1..buf.len()-1].iter().all(|&x| x == erased_val)); |
| } |
| } |
| |
| // Helper checks for the result type. |
| trait EChecker { |
| fn is_bounds(&self) -> bool; |
| } |
| |
| impl<T> EChecker for Result<T> { |
| |
| fn is_bounds(&self) -> bool { |
| match *self { |
| Err(FlashError::OutOfBounds(_)) => true, |
| _ => false, |
| } |
| } |
| } |
| } |