diff --git a/openemc-firmware/memory-big.x b/openemc-firmware/memory-big.x index f1c983b..4d325f9 100644 --- a/openemc-firmware/memory-big.x +++ b/openemc-firmware/memory-big.x @@ -31,4 +31,13 @@ SECTIONS } INSERT BEFORE .data; +SECTIONS +{ + .program_end (NOLOAD) : ALIGN(4) + { + __flash_program_end = .; + } > FLASH +} +INSERT AFTER .gnu.sgstubs; + __flash_end = ORIGIN(FLASH) + LENGTH(FLASH); diff --git a/openemc-firmware/memory-normal.x b/openemc-firmware/memory-normal.x index 1bd341a..e0c156f 100644 --- a/openemc-firmware/memory-normal.x +++ b/openemc-firmware/memory-normal.x @@ -31,4 +31,13 @@ SECTIONS } INSERT BEFORE .data; +SECTIONS +{ + .program_end (NOLOAD) : ALIGN(4) + { + __flash_program_end = .; + } > FLASH +} +INSERT AFTER .gnu.sgstubs; + __flash_end = ORIGIN(FLASH) + LENGTH(FLASH); diff --git a/openemc-firmware/memory-standalone.x b/openemc-firmware/memory-standalone.x index d52e9ae..03120d8 100644 --- a/openemc-firmware/memory-standalone.x +++ b/openemc-firmware/memory-standalone.x @@ -31,4 +31,13 @@ SECTIONS } INSERT BEFORE .data; +SECTIONS +{ + .program_end (NOLOAD) : ALIGN(4) + { + __flash_program_end = .; + } > FLASH +} +INSERT AFTER .gnu.sgstubs; + __flash_end = ORIGIN(FLASH) + LENGTH(FLASH); diff --git a/openemc-firmware/src/cfg.rs b/openemc-firmware/src/cfg.rs index 8a77f8e..b7e6dca 100644 --- a/openemc-firmware/src/cfg.rs +++ b/openemc-firmware/src/cfg.rs @@ -3,7 +3,7 @@ use core::{mem::MaybeUninit, ptr::addr_of_mut}; use defmt::Format; -use crate::flash_data::Validate; +use crate::flash_data::FlashData; /// Behavior when charger is attached while device is powered off. #[derive(Default, Format, Clone, Copy, PartialEq, Eq)] @@ -29,7 +29,7 @@ impl TryFrom for ChargerAttached { } /// Configuration stored in flash. -#[derive(Format, Clone, Copy)] +#[derive(Format, Clone, Copy, PartialEq, Eq)] #[repr(C)] pub struct Cfg { /// Behavior when charger is attached while device is powered off. @@ -40,14 +40,7 @@ pub struct Cfg { pub prohibit_power_off: bool, } -impl Cfg { - /// Log configuration change. - pub fn log(&self) { - defmt::info!("Configuration changed: {:?}", self); - } -} - -impl Validate for Cfg { +impl FlashData for Cfg { fn validate(mut this: MaybeUninit) -> Self { unsafe { let ptr = this.as_mut_ptr(); @@ -65,4 +58,8 @@ impl Validate for Cfg { this.assume_init() } } + + fn notify_changed(&self) { + defmt::info!("Configuration changed: {:?}", self); + } } diff --git a/openemc-firmware/src/flash_data.rs b/openemc-firmware/src/flash_data.rs index f0d0b44..d63129f 100644 --- a/openemc-firmware/src/flash_data.rs +++ b/openemc-firmware/src/flash_data.rs @@ -2,7 +2,7 @@ use core::{ mem::{size_of, zeroed, MaybeUninit}, - ops::{Deref, DerefMut}, + ops::Deref, slice, }; use defmt::assert; @@ -11,10 +11,12 @@ use stm32f1xx_hal::flash::FlashWriter; use crate::{flash_page_size, util::FlashUtil}; -/// Validate data loaded from flash. -pub trait Validate: Sized { +/// Data storable in flash. +pub trait FlashData: Sized + Copy + Eq + Sized + 'static { /// Validate and fix data as needed so that it becomes a valid Rust value. fn validate(this: MaybeUninit) -> Self; + /// Notify data that it was changed (for logging). + fn notify_changed(&self) {} } #[derive(Clone, Copy, PartialEq, Eq)] @@ -35,12 +37,14 @@ pub struct FlashBackened { impl FlashBackened where - T: Validate + Copy + Sized + 'static, + T: FlashData, { const HEADER_SIZE: usize = 8; /// Create copies at specified offsets in flash. - pub fn new_at(flash: &mut FlashWriter, flash_offset1: u32, flash_offset2: u32, reserved: usize) -> Self { + pub fn new_at( + flash: &mut FlashWriter, flash_offset1: u32, flash_offset2: u32, reserved: usize, erase: bool, + ) -> Self { assert!(size_of::() % 2 == 0, "data must have even size"); assert!(reserved % flash_page_size() == 0, "reserved size must be a multiple of flash page size"); assert!( @@ -58,15 +62,21 @@ where version: 0, next_save: FlashCopy::Copy1, }; - this.load(flash); + + if erase { + this.erase(flash); + } else { + this.load(flash); + } + this } /// Create copies with end offset in flash specified. - pub fn new_at_end(flash: &mut FlashWriter, reserved: usize, end_offset: u32) -> Self { + pub fn new_at_end(flash: &mut FlashWriter, reserved: usize, end_offset: u32, erase: bool) -> Self { let flash_offset2 = end_offset - reserved as u32; let flash_offset1 = flash_offset2 - reserved as u32; - Self::new_at(flash, flash_offset1, flash_offset2, reserved) + Self::new_at(flash, flash_offset1, flash_offset2, reserved, erase) } /// Read copy from flash and verify CRC32. @@ -125,11 +135,12 @@ where self.version = version; self.data = T::validate(data); + self.data.notify_changed(); self.next_save = next_save; } /// Save data to flash. - pub fn save(&mut self, flash: &mut FlashWriter) { + fn save(&mut self, flash: &mut FlashWriter) { self.version = self.version.wrapping_add(1); self.next_save = match self.next_save { FlashCopy::Copy1 => { @@ -143,10 +154,28 @@ where }; } - /// Erase data in flash. - pub fn erase(&self, flash: &mut FlashWriter) { - flash.erase_unwrap(self.flash_offset1, self.reserved + Self::HEADER_SIZE); - flash.erase_unwrap(self.flash_offset2, self.reserved + Self::HEADER_SIZE); + /// Erase data in flash and init with defaults. + pub fn erase(&mut self, flash: &mut FlashWriter) { + flash.erase_unwrap(self.flash_offset1, self.reserved); + flash.erase_unwrap(self.flash_offset2, self.reserved); + + self.version = 0; + self.data = T::validate(unsafe { zeroed() }); + self.data.notify_changed(); + self.next_save = FlashCopy::Copy1; + } + + /// Modify data and save to flash if changed. + pub fn modify(&mut self, flash: &mut FlashWriter, modify: impl FnOnce(&mut T)) { + let old_data = self.data; + modify(&mut self.data); + + if old_data == self.data { + return; + } + + self.data.notify_changed(); + self.save(flash); } } @@ -157,9 +186,3 @@ impl Deref for FlashBackened { &self.data } } - -impl DerefMut for FlashBackened { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.data - } -} diff --git a/openemc-firmware/src/main.rs b/openemc-firmware/src/main.rs index c2cddce..4beabd9 100644 --- a/openemc-firmware/src/main.rs +++ b/openemc-firmware/src/main.rs @@ -149,6 +149,9 @@ static mut BOOTLOADER_LOG_REF: Option< > = None; extern "C" { + /// End of program in flash (from linker). + pub static __flash_program_end: c_void; + /// End of flash (from linker). pub static __flash_end: c_void; @@ -156,6 +159,11 @@ extern "C" { pub static __flash_page_size: c_void; } +/// End of program in flash. +pub fn flash_program_end() -> usize { + unsafe { &__flash_program_end as *const _ as usize } +} + /// End of user flash. pub fn flash_end() -> usize { unsafe { &__flash_end as *const _ as usize } @@ -357,8 +365,14 @@ mod app { let bi = BootInfo::get(); // Load configuration from flash. + let cfg_size = flash_page_size(); + defmt::assert!(flash_program_end() <= flash_end() - 2 * cfg_size, "no space for configuration in flash"); + let erase_cfg = bi.boot_reason == BootReason::FactoryReset as _; + if erase_cfg { + defmt::info!("Erasing configuration due to factory reset"); + } let mut fw = FlashWriter::new(&mut flash); - let cfg = FlashBackened::new_at_end(&mut fw, flash_page_size(), flash_end() as u32 - FLASH_START); + let cfg = FlashBackened::new_at_end(&mut fw, cfg_size, flash_end() as u32 - FLASH_START, erase_cfg); // Create board handler. defmt::info!("board new"); @@ -1409,20 +1423,18 @@ mod app { respond_u8(cx.shared.cfg.lock(|cfg| cfg.prohibit_power_off.into())) } Event::Write { reg: reg::POWER_OFF_PROHIBITED, value } => { - (cx.shared.flash, cx.shared.cfg).lock(|mut flash, cfg| { - cfg.prohibit_power_off = value.as_u8() != 0; - cfg.save(&mut FlashWriter::new(&mut flash)); - cfg.log(); + (cx.shared.flash, cx.shared.cfg).lock(|flash, cfg| { + cfg.modify(&mut FlashWriter::new(flash), |cfg| cfg.prohibit_power_off = value.as_u8() != 0); }) } Event::Read { reg: reg::POWER_ON_BY_CHARGING } => { respond_u8(cx.shared.cfg.lock(|cfg| cfg.charger_attached as u8)) } Event::Write { reg: reg::POWER_ON_BY_CHARGING, value } => { - (cx.shared.flash, cx.shared.cfg).lock(|mut flash, cfg| { - cfg.charger_attached = value.as_u8().try_into().unwrap_or_default(); - cfg.save(&mut FlashWriter::new(&mut flash)); - cfg.log(); + (cx.shared.flash, cx.shared.cfg).lock(|flash, cfg| { + cfg.modify(&mut FlashWriter::new(flash), |cfg| { + cfg.charger_attached = value.as_u8().try_into().unwrap_or_default() + }); }) } Event::Read { reg: reg::PWM_TIMERS } => respond_u8(cx.local.pwm_timers.len() as u8), diff --git a/openemc-firmware/src/util.rs b/openemc-firmware/src/util.rs index 47b7459..e1a00db 100644 --- a/openemc-firmware/src/util.rs +++ b/openemc-firmware/src/util.rs @@ -85,6 +85,7 @@ pub fn unique_device_id() -> u128 { /// Flash utility function. pub trait FlashUtil { /// Create flash writer. + #[allow(clippy::new_ret_no_self)] fn new(parts: &mut flash::Parts) -> FlashWriter; /// Read and panic if failed.