use alloc::vec::Vec;
use core::ops::Range;
use crate::error::{Error, Result};
pub const DEFAULT_PAGE_SIZE: usize = 4096;
pub const DEFAULT_NUM_PAGES: usize = 16;
pub const PROGRAM_PAGE_SIZE: usize = 256;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PageId(pub usize);
impl PageId {
pub fn to_offset(&self, page_size: usize) -> usize {
self.0 * page_size
}
pub fn is_valid(&self, num_pages: usize) -> bool {
self.0 < num_pages
}
}
#[derive(Clone, Debug)]
pub struct FlashConfig {
pub page_size: usize,
pub num_pages: usize,
pub program_size: usize,
pub max_erase_cycles: u32,
}
impl Default for FlashConfig {
fn default() -> Self {
Self {
page_size: DEFAULT_PAGE_SIZE,
num_pages: DEFAULT_NUM_PAGES,
program_size: PROGRAM_PAGE_SIZE,
max_erase_cycles: 100_000,
}
}
}
#[derive(Debug)]
pub struct Flash {
config: FlashConfig,
data: Vec<u8>,
erase_counts: Vec<u32>,
erased: Vec<bool>, }
impl Flash {
pub fn new() -> Self {
Self::with_config(FlashConfig::default())
}
pub fn with_config(config: FlashConfig) -> Self {
let num_pages = config.num_pages;
let size = config.page_size * num_pages;
Self {
config,
data: vec![0xFF; size],
erase_counts: vec![0; num_pages],
erased: vec![true; num_pages],
}
}
pub fn capacity(&self) -> usize {
self.config.page_size * self.config.num_pages
}
pub fn page_size(&self) -> usize {
self.config.page_size
}
pub fn erase_page(&mut self, page: PageId) -> Result<()> {
if !page.is_valid(self.config.num_pages) {
return Err(Error::flash(format!(
"page {} out of range (0-{})",
page.0, self.config.num_pages - 1
)));
}
if self.erase_counts[page.0] >= self.config.max_erase_cycles {
return Err(Error::flash(format!(
"page {} exceeded max erase cycles ({})",
page.0, self.config.max_erase_cycles
)));
}
let start = page.to_offset(self.config.page_size);
self.data[start..start + self.config.page_size].fill(0xFF);
self.erase_counts[page.0] += 1;
self.erased[page.0] = true;
Ok(())
}
pub fn write(&mut self, offset: usize, data: &[u8]) -> Result<()> {
if offset.saturating_add(data.len()) > self.capacity() {
return Err(Error::flash(format!(
"write out of bounds: offset={}, len={}, capacity={}",
offset, data.len(), self.capacity()
)));
}
for (i, &byte) in data.iter().enumerate() {
let current = self.data[offset + i];
if byte & !current != 0 {
let page = (offset + i) / self.config.page_size;
return Err(Error::flash(format!(
"write violation at offset {}: attempting 0->1 transition (page {} not erased?)",
offset + i, page
)));
}
}
self.data[offset..offset + data.len()].copy_from_slice(data);
let start_page = offset / self.config.page_size;
let end_page = (offset + data.len()) / self.config.page_size;
for p in start_page..=end_page {
if p < self.config.num_pages {
self.erased[p] = false;
}
}
Ok(())
}
pub fn read(&self, offset: usize, len: usize) -> Result<&[u8]> {
if offset.saturating_add(len) > self.capacity() {
return Err(Error::flash("read out of bounds"));
}
Ok(&self.data[offset..offset + len])
}
pub fn get_erase_count(&self, page: PageId) -> Option<u32> {
self.erase_counts.get(page.0).copied()
}
pub fn is_erased(&self, page: PageId) -> bool {
self.erased.get(page.0).copied().unwrap_or(false)
}
pub fn find_wear_leveled_pages(&self, count: usize) -> Vec<PageId> {
let mut indexed: Vec<_> = self.erase_counts.iter()
.enumerate()
.map(|(i, &count)| (count, i))
.collect();
indexed.sort();
indexed.iter().take(count).map(|(_, i)| PageId(*i)).collect()
}
pub fn total_erase_cycles(&self) -> u64 {
self.erase_counts.iter().map(|&c| c as u64).sum()
}
#[cfg(feature = "std")]
pub fn dump(&self, start: usize, len: usize) -> String {
use std::format;
let end = (start + len).min(self.capacity());
let mut result = String::new();
for chunk in self.data[start..end].chunks(16) {
for byte in chunk {
result.push_str(&format!("{:02x} ", byte));
}
result.push('\n');
}
result
}
}
impl Default for Flash {
fn default() -> Self {
Self::new()
}
}