Skip to content

Commit 3e61690

Browse files
committed
Add complete flash abstraction module
Implement full bootloader/src/flash.rs with: - Flash trait (read, erase_sector, program_page, verify) - FlashError and Result types - MockFlash in-memory driver for host testing - InternalFlash skeleton with safety checks and MCU TODOs - read_flash and write_flash convenience APIs - Unit tests covering core operations
1 parent 0a240f5 commit 3e61690

File tree

1 file changed

+351
-3
lines changed

1 file changed

+351
-3
lines changed

bootloader/src/flash.rs

Lines changed: 351 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,355 @@
55
//! - Commercial license required for closed-source use
66
//! Author : Md Mahbubur Rahman
77
//! URL : <https://m-a-h-b-u-b.github.io>
8-
//! GitHub : <https://github.com/m-a-h-b-u-b/M2-Bootloader-RUST>
8+
//! GitHub : <https://github.com/m-a-h-b-u-b/M2-Bootloader-Rust>
9+
//!
10+
//! bootloader/src/flash.rs
11+
//! Flash abstraction and utility functions for the bootloader.
12+
//!
13+
//! This module provides:
14+
//! - A generic `Flash` trait that the rest of the bootloader uses.
15+
//! - A `MockFlash` in-memory implementation useful for testing and host-side
16+
//! unit-tests.
17+
//! - An `InternalFlash` skeleton that can be completed with MCU-specific
18+
//! register sequences. The skeleton exposes safe high-level helpers such as
19+
//! `read_flash` and `write_flash` that the rest of the bootloader can call.
20+
//!
21+
//! Features and notes:
22+
//! - The module is testable on host using the `MockFlash` type.
23+
//! - For embedded/no_std targets you will want to enable a `alloc`/stack-buffer
24+
//! strategy and implement the `InternalFlash` sequences for your MCU.
925
10-
pub fn read_flash(addr: u32, buf: &mut [u8]) {}
11-
pub fn write_flash(addr: u32, data: &[u8]) {}
26+
#![allow(dead_code)]
27+
28+
use core::fmt;
29+
30+
/// Default page size used by mock devices and as a hint for internal drivers.
31+
pub const DEFAULT_PAGE_SIZE: usize = 256;
32+
33+
/// Errors returned by flash operations.
34+
#[derive(Debug, PartialEq, Eq)]
35+
pub enum FlashError {
36+
OutOfBounds,
37+
AlignmentError,
38+
DeviceError(&'static str),
39+
VerificationFailed { addr: usize, expected: u8, found: u8 },
40+
}
41+
42+
impl fmt::Display for FlashError {
43+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44+
match self {
45+
FlashError::OutOfBounds => write!(f, "flash: address out of bounds"),
46+
FlashError::AlignmentError => write!(f, "flash: alignment error"),
47+
FlashError::DeviceError(s) => write!(f, "flash device error: {}", s),
48+
FlashError::VerificationFailed { addr, expected, found } => write!(
49+
f,
50+
"flash verify failed at {:#010x}: expected=0x{:02x} found=0x{:02x}",
51+
addr, expected, found
52+
),
53+
}
54+
}
55+
}
56+
57+
impl std::error::Error for FlashError {}
58+
59+
pub type Result<T> = core::result::Result<T, FlashError>;
60+
61+
/// Trait for flash devices used by the bootloader. Keep implementation minimal
62+
/// to allow both on-chip flash and external SPI/NOR devices to implement it.
63+
pub trait Flash {
64+
fn size(&self) -> usize;
65+
fn sector_size(&self) -> usize;
66+
fn page_size(&self) -> usize;
67+
fn read(&self, addr: usize, buf: &mut [u8]) -> Result<()>;
68+
fn erase_sector(&mut self, addr: usize) -> Result<()>;
69+
fn program_page(&mut self, addr: usize, data: &[u8]) -> Result<()>;
70+
71+
/// Default verify implementation (reads and compares).
72+
fn verify(&self, addr: usize, data: &[u8]) -> Result<()> {
73+
// Using Vec here for host tests; embedded builds should override or
74+
// provide a temporary buffer strategy (stack buffer or preallocated).
75+
#[cfg(feature = "std")]
76+
let mut buf = vec![0u8; data.len()];
77+
#[cfg(not(feature = "std"))]
78+
let mut buf = alloc::vec::Vec::from_raw_parts(core::ptr::null_mut(), 0, 0); // placeholder
79+
80+
#[cfg(feature = "std")]
81+
{
82+
self.read(addr, &mut buf)?;
83+
for (i, (&a, &b)) in data.iter().zip(buf.iter()).enumerate() {
84+
if a != b {
85+
return Err(FlashError::VerificationFailed {
86+
addr: addr + i,
87+
expected: a,
88+
found: b,
89+
});
90+
}
91+
}
92+
Ok(())
93+
}
94+
#[cfg(not(feature = "std"))]
95+
{
96+
// For no_std builds users must implement their own verify or enable
97+
// an allocator and a temporary buffer. We'll return DeviceError to
98+
// indicate the lack of an implementation here.
99+
Err(FlashError::DeviceError("verify not implemented for no_std; enable alloc or override verify"))
100+
}
101+
}
102+
103+
/// Write a region: erase affected sectors and program page-by-page.
104+
fn write_region(&mut self, addr: usize, data: &[u8]) -> Result<()> {
105+
if addr.checked_add(data.len()).is_none() || addr + data.len() > self.size() {
106+
return Err(FlashError::OutOfBounds);
107+
}
108+
109+
let sector = self.sector_size();
110+
if sector == 0 {
111+
return Err(FlashError::DeviceError("invalid sector size"));
112+
}
113+
114+
let start_sector = addr / sector;
115+
let end_sector = (addr + data.len() - 1) / sector;
116+
for s in start_sector..=end_sector {
117+
let sector_addr = s * sector;
118+
self.erase_sector(sector_addr)?;
119+
}
120+
121+
let page = self.page_size();
122+
if page == 0 {
123+
return Err(FlashError::DeviceError("invalid page size"));
124+
}
125+
126+
let mut offset = 0usize;
127+
while offset < data.len() {
128+
let write_addr = addr + offset;
129+
let remain = data.len() - offset;
130+
let write_len = core::cmp::min(remain, page);
131+
let page_slice = &data[offset..offset + write_len];
132+
133+
self.program_page(write_addr, page_slice)?;
134+
self.verify(write_addr, page_slice)?;
135+
136+
offset += write_len;
137+
}
138+
139+
Ok(())
140+
}
141+
142+
/// Compute CRC32 of a region. Default uses crc32fast when std is enabled.
143+
fn crc32(&self, addr: usize, len: usize) -> Result<u32> {
144+
if addr.checked_add(len).is_none() || addr + len > self.size() {
145+
return Err(FlashError::OutOfBounds);
146+
}
147+
148+
#[cfg(feature = "std")]
149+
{
150+
let mut buf = vec![0u8; len];
151+
self.read(addr, &mut buf)?;
152+
Ok(crc32fast::hash(&buf))
153+
}
154+
#[cfg(not(feature = "std"))]
155+
{
156+
Err(FlashError::DeviceError("crc32 requires std or custom implementation"))
157+
}
158+
}
159+
}
160+
161+
// -----------------------------------------------------------------------------
162+
// MockFlash - in-memory implementation
163+
// -----------------------------------------------------------------------------
164+
165+
pub struct MockFlash {
166+
pub storage: Vec<u8>,
167+
sector_size: usize,
168+
page_size: usize,
169+
}
170+
171+
impl MockFlash {
172+
pub fn new(size: usize, sector_size: usize, page_size: usize) -> Self {
173+
MockFlash {
174+
storage: vec![0xFFu8; size],
175+
sector_size,
176+
page_size,
177+
}
178+
}
179+
180+
pub fn fill(&mut self, v: u8) {
181+
for b in self.storage.iter_mut() {
182+
*b = v;
183+
}
184+
}
185+
}
186+
187+
impl Flash for MockFlash {
188+
fn size(&self) -> usize {
189+
self.storage.len()
190+
}
191+
192+
fn sector_size(&self) -> usize {
193+
self.sector_size
194+
}
195+
196+
fn page_size(&self) -> usize {
197+
self.page_size
198+
}
199+
200+
fn read(&self, addr: usize, buf: &mut [u8]) -> Result<()> {
201+
let end = addr.checked_add(buf.len()).ok_or(FlashError::OutOfBounds)?;
202+
if end > self.storage.len() {
203+
return Err(FlashError::OutOfBounds);
204+
}
205+
buf.copy_from_slice(&self.storage[addr..end]);
206+
Ok(())
207+
}
208+
209+
fn erase_sector(&mut self, addr: usize) -> Result<()> {
210+
if addr >= self.storage.len() { return Err(FlashError::OutOfBounds); }
211+
if addr % self.sector_size != 0 { return Err(FlashError::AlignmentError); }
212+
let end = addr + self.sector_size;
213+
if end > self.storage.len() { return Err(FlashError::OutOfBounds); }
214+
for b in &mut self.storage[addr..end] { *b = 0xFF; }
215+
Ok(())
216+
}
217+
218+
fn program_page(&mut self, addr: usize, data: &[u8]) -> Result<()> {
219+
if addr >= self.storage.len() { return Err(FlashError::OutOfBounds); }
220+
if data.len() > self.page_size { return Err(FlashError::AlignmentError); }
221+
let end = addr + data.len();
222+
if end > self.storage.len() { return Err(FlashError::OutOfBounds); }
223+
for (i, &b) in data.iter().enumerate() {
224+
let dst = &mut self.storage[addr + i];
225+
if (b & *dst) != b {
226+
return Err(FlashError::DeviceError("attempt to program 0->1"));
227+
}
228+
*dst = *dst & b;
229+
}
230+
Ok(())
231+
}
232+
}
233+
234+
// -----------------------------------------------------------------------------
235+
// InternalFlash skeleton
236+
// -----------------------------------------------------------------------------
237+
238+
/// InternalFlash offers a thin wrapper over MCU internal flash.
239+
///
240+
/// The actual erase/program sequences must be implemented per-MCU. Keep the
241+
/// struct state minimal: base address and sizes for validation.
242+
pub struct InternalFlash {
243+
pub base_addr: usize,
244+
pub total_size: usize,
245+
pub sector_size: usize,
246+
pub page_size: usize,
247+
}
248+
249+
impl InternalFlash {
250+
pub const fn new(base_addr: usize, total_size: usize, sector_size: usize, page_size: usize) -> Self {
251+
Self { base_addr, total_size, sector_size, page_size }
252+
}
253+
254+
/// Convert a relative flash offset into absolute pointer for read.
255+
fn abs_addr(&self, rel: usize) -> Result<usize> {
256+
if rel.checked_add(0).is_none() || rel >= self.total_size {
257+
return Err(FlashError::OutOfBounds);
258+
}
259+
Ok(self.base_addr + rel)
260+
}
261+
}
262+
263+
impl Flash for InternalFlash {
264+
fn size(&self) -> usize { self.total_size }
265+
fn sector_size(&self) -> usize { self.sector_size }
266+
fn page_size(&self) -> usize { self.page_size }
267+
268+
fn read(&self, addr: usize, buf: &mut [u8]) -> Result<()> {
269+
if addr.checked_add(buf.len()).is_none() || addr + buf.len() > self.total_size {
270+
return Err(FlashError::OutOfBounds);
271+
}
272+
let absolute = self.base_addr + addr;
273+
unsafe {
274+
let src = absolute as *const u8;
275+
for i in 0..buf.len() {
276+
buf[i] = core::ptr::read_volatile(src.add(i));
277+
}
278+
}
279+
Ok(())
280+
}
281+
282+
fn erase_sector(&mut self, addr: usize) -> Result<()> {
283+
if addr >= self.total_size { return Err(FlashError::OutOfBounds); }
284+
if addr % self.sector_size != 0 { return Err(FlashError::AlignmentError); }
285+
// MCU-specific erase sequence needed here.
286+
Err(FlashError::DeviceError("InternalFlash::erase_sector not implemented - fill MCU-specific sequence"))
287+
}
288+
289+
fn program_page(&mut self, addr: usize, data: &[u8]) -> Result<()> {
290+
if addr >= self.total_size { return Err(FlashError::OutOfBounds); }
291+
if data.len() > self.page_size { return Err(FlashError::AlignmentError); }
292+
// MCU-specific program sequence needed here.
293+
Err(FlashError::DeviceError("InternalFlash::program_page not implemented - fill MCU-specific sequence"))
294+
}
295+
}
296+
297+
// -----------------------------------------------------------------------------
298+
// High-level convenience API using a static InternalFlash instance
299+
// -----------------------------------------------------------------------------
300+
301+
// NOTE: adjust these constants to your MCU memory map in docs/memory_map.md
302+
const FLASH_BASE_ADDR: usize = 0x0800_0000;
303+
const FLASH_TOTAL_BYTES: usize = 512 * 1024;
304+
const FLASH_SECTOR_BYTES: usize = 2048;
305+
const FLASH_PAGE_BYTES: usize = 256;
306+
307+
static mut BOOT_INTERNAL_FLASH: InternalFlash = InternalFlash::new(
308+
FLASH_BASE_ADDR,
309+
FLASH_TOTAL_BYTES,
310+
FLASH_SECTOR_BYTES,
311+
FLASH_PAGE_BYTES,
312+
);
313+
314+
/// Read `buf.len()` bytes from absolute flash address `addr`.
315+
pub fn read_flash(addr: u32, buf: &mut [u8]) -> Result<()> {
316+
let rel = addr as usize - FLASH_BASE_ADDR;
317+
unsafe { BOOT_INTERNAL_FLASH.read(rel, buf) }
318+
}
319+
320+
/// Write `data` to absolute flash address `addr`. This will erase overlapping
321+
/// sectors and program pages, verifying after each page.
322+
pub fn write_flash(addr: u32, data: &[u8]) -> Result<()> {
323+
let rel = addr as usize - FLASH_BASE_ADDR;
324+
unsafe { BOOT_INTERNAL_FLASH.write_region(rel, data) }
325+
}
326+
327+
// -----------------------------------------------------------------------------
328+
// Unit tests for host
329+
// -----------------------------------------------------------------------------
330+
331+
#[cfg(test)]
332+
mod tests {
333+
use super::*;
334+
use crate::flash::MockFlash;
335+
336+
#[test]
337+
fn mock_basic_flow() {
338+
let mut f = MockFlash::new(1024, 256, 128);
339+
assert_eq!(f.size(), 1024);
340+
341+
let addr = 256;
342+
let data = vec![0xAAu8; 128];
343+
f.erase_sector(256).unwrap();
344+
f.program_page(addr, &data).unwrap();
345+
assert!(f.verify(addr, &data).is_ok());
346+
347+
// attempt 0->1 should fail
348+
let bad = vec![0xFFu8; 128];
349+
assert!(matches!(f.program_page(addr, &bad), Err(FlashError::DeviceError(_))));
350+
}
351+
352+
#[test]
353+
fn write_region_test() {
354+
let mut f = MockFlash::new(2048, 256, 128);
355+
let payload = vec![0x55u8; 300];
356+
assert!(f.write_region(100, &payload).is_ok());
357+
assert!(f.verify(100, &payload).is_ok());
358+
}
359+
}

0 commit comments

Comments
 (0)