Skip to main content

speck_core/
flash.rs

1//! NOR Flash abstraction with wear leveling and transaction support
2//! 
3//! Models typical small SPI NOR flash chips:
4//! - 4KB pages (erase granularity)
5//! - 256B write granularity (program page size)
6//! - Limited erase cycles (~100k)
7
8use alloc::vec::Vec;
9use core::ops::Range;
10use crate::error::{Error, Result};
11
12/// Default page size (4KB)
13pub const DEFAULT_PAGE_SIZE: usize = 4096;
14
15/// Default number of pages (16 = 64KB)
16pub const DEFAULT_NUM_PAGES: usize = 16;
17
18/// Program page size (256 bytes)
19pub const PROGRAM_PAGE_SIZE: usize = 256;
20
21/// Page identifier (0-based)
22#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
23pub struct PageId(pub usize);
24
25impl PageId {
26    /// Convert to byte offset
27    pub fn to_offset(&self, page_size: usize) -> usize {
28        self.0 * page_size
29    }
30    
31    /// Check if valid for given flash size
32    pub fn is_valid(&self, num_pages: usize) -> bool {
33        self.0 < num_pages
34    }
35}
36
37/// Flash configuration
38#[derive(Clone, Debug)]
39pub struct FlashConfig {
40    /// Page size in bytes (erase unit)
41    pub page_size: usize,
42    /// Number of pages
43    pub num_pages: usize,
44    /// Program page size (write unit)
45    pub program_size: usize,
46    /// Maximum erase cycles per page
47    pub max_erase_cycles: u32,
48}
49
50impl Default for FlashConfig {
51    fn default() -> Self {
52        Self {
53            page_size: DEFAULT_PAGE_SIZE,
54            num_pages: DEFAULT_NUM_PAGES,
55            program_size: PROGRAM_PAGE_SIZE,
56            max_erase_cycles: 100_000,
57        }
58    }
59}
60
61/// Simulated NOR Flash storage
62#[derive(Debug)]
63pub struct Flash {
64    config: FlashConfig,
65    data: Vec<u8>,
66    erase_counts: Vec<u32>,
67    erased: Vec<bool>, // Tracks which pages are in erased state (0xFF)
68}
69
70impl Flash {
71    /// Create new flash simulation with default config (64KB)
72    pub fn new() -> Self {
73        Self::with_config(FlashConfig::default())
74    }
75    
76    /// Create with custom configuration
77    pub fn with_config(config: FlashConfig) -> Self {
78        let num_pages = config.num_pages;
79        let size = config.page_size * num_pages;
80        Self {
81            config,
82            data: vec![0xFF; size],
83            erase_counts: vec![0; num_pages],
84            erased: vec![true; num_pages],
85        }
86    }
87    
88    /// Get total capacity
89    pub fn capacity(&self) -> usize {
90        self.config.page_size * self.config.num_pages
91    }
92    
93    /// Get page size
94    pub fn page_size(&self) -> usize {
95        self.config.page_size
96    }
97    
98    /// Erase a page (sets all bytes to 0xFF)
99    /// 
100    /// # Errors
101    /// Returns error if page index is out of range or max erase cycles exceeded
102    pub fn erase_page(&mut self, page: PageId) -> Result<()> {
103        if !page.is_valid(self.config.num_pages) {
104            return Err(Error::flash(format!(
105                "page {} out of range (0-{})", 
106                page.0, self.config.num_pages - 1
107            )));
108        }
109        
110        if self.erase_counts[page.0] >= self.config.max_erase_cycles {
111            return Err(Error::flash(format!(
112                "page {} exceeded max erase cycles ({})",
113                page.0, self.config.max_erase_cycles
114            )));
115        }
116        
117        let start = page.to_offset(self.config.page_size);
118        self.data[start..start + self.config.page_size].fill(0xFF);
119        self.erase_counts[page.0] += 1;
120        self.erased[page.0] = true;
121        
122        Ok(())
123    }
124    
125    /// Write data to flash
126    /// 
127    /// Flash can only change bits from 1 to 0, not 0 to 1.
128    /// Attempting to set a bit from 0 to 1 returns an error.
129    pub fn write(&mut self, offset: usize, data: &[u8]) -> Result<()> {
130        if offset.saturating_add(data.len()) > self.capacity() {
131            return Err(Error::flash(format!(
132                "write out of bounds: offset={}, len={}, capacity={}",
133                offset, data.len(), self.capacity()
134            )));
135        }
136        
137        // Check for write violations (0 -> 1 transitions)
138        for (i, &byte) in data.iter().enumerate() {
139            let current = self.data[offset + i];
140            if byte & !current != 0 {
141                let page = (offset + i) / self.config.page_size;
142                return Err(Error::flash(format!(
143                    "write violation at offset {}: attempting 0->1 transition (page {} not erased?)",
144                    offset + i, page
145                )));
146            }
147        }
148        
149        self.data[offset..offset + data.len()].copy_from_slice(data);
150        
151        // Mark affected pages as modified (not fully erased)
152        let start_page = offset / self.config.page_size;
153        let end_page = (offset + data.len()) / self.config.page_size;
154        for p in start_page..=end_page {
155            if p < self.config.num_pages {
156                self.erased[p] = false;
157            }
158        }
159        
160        Ok(())
161    }
162    
163    /// Read data from flash
164    pub fn read(&self, offset: usize, len: usize) -> Result<&[u8]> {
165        if offset.saturating_add(len) > self.capacity() {
166            return Err(Error::flash("read out of bounds"));
167        }
168        Ok(&self.data[offset..offset + len])
169    }
170    
171    /// Get erase count for a page
172    pub fn get_erase_count(&self, page: PageId) -> Option<u32> {
173        self.erase_counts.get(page.0).copied()
174    }
175    
176    /// Check if page is in erased state
177    pub fn is_erased(&self, page: PageId) -> bool {
178        self.erased.get(page.0).copied().unwrap_or(false)
179    }
180    
181    /// Find pages with lowest erase counts (for wear leveling)
182    pub fn find_wear_leveled_pages(&self, count: usize) -> Vec<PageId> {
183        let mut indexed: Vec<_> = self.erase_counts.iter()
184            .enumerate()
185            .map(|(i, &count)| (count, i))
186            .collect();
187        indexed.sort();
188        indexed.iter().take(count).map(|(_, i)| PageId(*i)).collect()
189    }
190    
191    /// Get total erase cycles across all pages
192    pub fn total_erase_cycles(&self) -> u64 {
193        self.erase_counts.iter().map(|&c| c as u64).sum()
194    }
195    
196    /// Dump flash contents as hex (for debugging)
197    #[cfg(feature = "std")]
198    pub fn dump(&self, start: usize, len: usize) -> String {
199        use std::format;
200        let end = (start + len).min(self.capacity());
201        let mut result = String::new();
202        for chunk in self.data[start..end].chunks(16) {
203            for byte in chunk {
204                result.push_str(&format!("{:02x} ", byte));
205            }
206            result.push('\n');
207        }
208        result
209    }
210}
211
212impl Default for Flash {
213    fn default() -> Self {
214        Self::new()
215    }
216}