Initial commit
Initial commit.
This commit is contained in:
1
bootloader/mcuboot/sim/simflash/.gitignore
vendored
Normal file
1
bootloader/mcuboot/sim/simflash/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Cargo.lock
|
||||
10
bootloader/mcuboot/sim/simflash/Cargo.toml
Normal file
10
bootloader/mcuboot/sim/simflash/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "simflash"
|
||||
version = "0.1.0"
|
||||
authors = ["David Brown <david.brown@linaro.org>"]
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
rand = "0.8"
|
||||
log = "0.4"
|
||||
thiserror = "1.0"
|
||||
377
bootloader/mcuboot/sim/simflash/src/lib.rs
Normal file
377
bootloader/mcuboot/sim/simflash/src/lib.rs
Normal file
@@ -0,0 +1,377 @@
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
80
bootloader/mcuboot/sim/simflash/src/pdump.rs
Normal file
80
bootloader/mcuboot/sim/simflash/src/pdump.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) 2017,2021 Linaro LTD
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Printable hexdump.
|
||||
|
||||
pub trait HexDump {
|
||||
// Output the data value in hex.
|
||||
fn dump(&self);
|
||||
}
|
||||
|
||||
struct Dumper {
|
||||
hex: String,
|
||||
ascii: String,
|
||||
count: usize,
|
||||
total_count: usize,
|
||||
}
|
||||
|
||||
impl Dumper {
|
||||
fn new() -> Dumper {
|
||||
Dumper {
|
||||
hex: String::with_capacity(49),
|
||||
ascii: String::with_capacity(16),
|
||||
count: 0,
|
||||
total_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn add_byte(&mut self, ch: u8) {
|
||||
if self.count == 16 {
|
||||
self.ship();
|
||||
}
|
||||
if self.count == 8 {
|
||||
self.hex.push(' ');
|
||||
}
|
||||
self.hex.push_str(&format!(" {:02x}", ch)[..]);
|
||||
self.ascii.push(if (b' '..=b'~').contains(&ch) {
|
||||
ch as char
|
||||
} else {
|
||||
'.'
|
||||
});
|
||||
self.count += 1;
|
||||
}
|
||||
|
||||
fn ship(&mut self) {
|
||||
if self.count == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
println!("{:06x} {:-49} |{}|", self.total_count, self.hex, self.ascii);
|
||||
|
||||
self.hex.clear();
|
||||
self.ascii.clear();
|
||||
self.total_count += 16;
|
||||
self.count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> HexDump for &'a [u8] {
|
||||
fn dump(&self) {
|
||||
let mut dump = Dumper::new();
|
||||
for ch in self.iter() {
|
||||
dump.add_byte(*ch);
|
||||
}
|
||||
dump.ship();
|
||||
}
|
||||
}
|
||||
|
||||
impl HexDump for Vec<u8> {
|
||||
fn dump(&self) {
|
||||
(&self[..]).dump()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn samples() {
|
||||
"Hello".as_bytes().dump();
|
||||
"This is a much longer string".as_bytes().dump();
|
||||
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f".as_bytes().dump();
|
||||
}
|
||||
Reference in New Issue
Block a user