use crate::flash::Error as FlashError; use crate::flash::Flash; use sfkv::{Store, StoreBackend}; use stm32h7xx_hal::pac::FLASH; use log::error; // Use the first 8KiB of last sector of bank 2 for config // i.e. 0x081E0000 to 0x081E2000 // The sector size might be too large for `compact()` to work with intended benefit. pub const FLASH_SECTOR_SIZE: usize = 0x2000; pub const FLASH_BANK: u8 = 2; pub const FLASH_SECTOR: u8 = 7; pub const FLASH_SECTOR_OFFSET: usize = 0x1E0000; pub static mut BACKUP_SPACE: [u8; FLASH_SECTOR_SIZE] = [0x00; FLASH_SECTOR_SIZE]; // Middle space will pretend to be the flash (i.e. the fake flash, or a very large cache) // STM32H7 flash is an abomination. // static mut MIDDLE_SPACE: [u8; FLASH_SECTOR_SIZE] = [0xFF; FLASH_SECTOR_SIZE]; #[derive(Debug, Clone, Copy, PartialEq)] pub enum SFKVProcessType { Length, Type, Space, Data, } // Manage access to flash memory // Motivation: STM32H7 flash only accepts 32-bytes transaction, SFKV mandates byte-by-byte access // A middle layer is needed to inplement SFKV, that is acceptable to STM32H7 flash // Idea: Forces BACKUP_SPACE to act as a cache. pub struct FakeFlashManager { // FSM: Track the type of data being programmed process_type: SFKVProcessType, } impl StoreBackend for FakeFlashManager { type Data = [u8]; // Return fn data(&self) -> &Self::Data { unsafe { &BACKUP_SPACE } } type Error = FlashError; fn erase(&mut self) -> Result<(), Self::Error> { self.reset_state(); Ok(()) } fn program(&mut self, offset: usize, payload: &[u8]) -> Result<(), Self::Error> { // Skip programming if payload is 0 length if payload.len() == 0 { self.advance_state(); return Ok(()); } // Examine payload address // If it is not identical to the programming address of the bankup space, // Program trailing bytes of 0xFF to prevent misinterpretation of data. unsafe { let cache_ptr: *const u8 = &BACKUP_SPACE[offset] as *const u8; let payload_ptr: *const u8 = &(*payload)[0] as *const u8; BACKUP_SPACE[offset..(offset + payload.len())].copy_from_slice(payload); // This part program extra trailing termination bytes (4 0xFF s) // However, if the remaining space is too small, there is also no need to program these bytes // Bytes that are too short will not be picked up by the iterator in sfkv. // If the data is programmed back to the exact same spot, // then we can assert that no k-v pair were ditched // Then there is no concern of accidentally interpreting unused flash memory as malformatted. if (cache_ptr != payload_ptr) && (offset + payload.len() + 4 <= FLASH_SECTOR_SIZE) && self.process_type == SFKVProcessType::Data { BACKUP_SPACE[(offset + payload.len())..(offset + payload.len() + 4)] .copy_from_slice(&[0xFF; 4]); } } self.advance_state(); Ok(()) } fn backup_space(&self) -> &'static mut [u8] { unsafe { &mut BACKUP_SPACE } } } impl FakeFlashManager { pub fn new() -> Self { Self { process_type: SFKVProcessType::Length, } } pub fn advance_state(&mut self) { self.process_type = match self.process_type { SFKVProcessType::Length => SFKVProcessType::Type, SFKVProcessType::Type => SFKVProcessType::Space, SFKVProcessType::Space => SFKVProcessType::Data, SFKVProcessType::Data => SFKVProcessType::Length, }; } pub fn reset_state(&mut self) { self.process_type = SFKVProcessType::Length; } } pub type FlashStore = Store; fn init_flash_cache(flash: FLASH) -> Flash { let flash = Flash::new(flash); // Initialize backup space // Now backup space acts like a cache, // sfkv will perform in-place operation in cache // flash will only be updated after invoking `save()` unsafe { BACKUP_SPACE.copy_from_slice(flash.read(FLASH_SECTOR_OFFSET, FLASH_SECTOR_SIZE)); } flash } fn init_flash_store() -> FlashStore { let ffm = FakeFlashManager::new(); let mut store = FlashStore::new(ffm); // just try to read the store match store.get_bytes_used() { Ok(_) => {} Err(e) => { error!("corrupt store, erasing. error: {:?}", e); let _ = store .erase() .map_err(|e| error!("flash erase failed: {:?}", e)); } } store } pub fn init_flash(flash: FLASH) -> (Flash, FlashStore) { let flash = init_flash_cache(flash); let store = init_flash_store(); (flash, store) } pub fn update_flash(flash: &mut Flash, store: &FlashStore) -> Result<(), FlashError> { flash.erase_sector(FLASH_BANK, FLASH_SECTOR)?; let flash_size = store.get_bytes_used().unwrap(); let save_size = if (flash_size % 0x20) == 0 { flash_size } else { flash_size + 0x20 - (flash_size % 0x20) }; unsafe { flash .program(FLASH_SECTOR_OFFSET, &BACKUP_SPACE[..save_size]) .map(|_| ()) } }