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