fix(f722): bound flash operations from sram
This commit is contained in:
1 parent
9fec2fa095
commit
1c90870634
4 files changed
+302
-63
No files matched your search
@@ -197,7 +197,10 @@ const F722_FIRST_LARGE_SECTOR: usize = 5;
|
||||
const FLASH_KEY1: u32 = 0x4567_0123;
|
||||
const FLASH_KEY2: u32 = 0xcdef_89ab;
|
||||
const FLASH_ERROR_FLAGS: u32 = 0x01f2;
|
||||
const BUSY_POLL_LIMIT: u32 = 1_000_000_000;
|
||||
// The F722 datasheet bounds one x32 128-KiB sector erase at 1.322 seconds.
|
||||
// The already-running 216-MHz DWT counter gives every operation a two-second
|
||||
// deadline without turning an instruction-count guess into a timing contract.
|
||||
const FLASH_OPERATION_TIMEOUT_CYCLES: u32 = 2 * 216_000_000;
|
||||
|
||||
/// One schema-independent journal shared by every STM32F722 board image.
|
||||
pub(crate) type F722ConfigStore<C, I, const MAX_PAYLOAD_BYTES: usize> =
|
||||
@@ -212,12 +215,16 @@ pub(crate) type F722ConfigManager<C, I, const MAX_PAYLOAD_BYTES: usize> =
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(crate) enum F722FlashError {
|
||||
Bounds,
|
||||
BusyTimeout,
|
||||
Unlock,
|
||||
Controller,
|
||||
Verification,
|
||||
}
|
||||
|
||||
enum F722FlashOperation {
|
||||
Erase { sector: u8 },
|
||||
Program { address: usize, word: u32 },
|
||||
}
|
||||
|
||||
/// Sole owner of the F722 flash controller used by the configuration journal.
|
||||
pub(crate) struct F722Nor {
|
||||
flash: stm32f722::FLASH,
|
||||
@@ -275,20 +282,106 @@ impl F722Nor {
|
||||
.map_err(|_| F722FlashError::Bounds)
|
||||
}
|
||||
|
||||
fn wait_ready(&self) -> Result<(), F722FlashError> {
|
||||
let mut remaining = BUSY_POLL_LIMIT;
|
||||
/// Executes one complete Flash operation from SRAM with IRQs masked.
|
||||
///
|
||||
/// A controller that remains busy beyond the datasheet bound cannot be
|
||||
/// recovered by writing `FLASH_CR`: RM0431 specifies that such a write
|
||||
/// stalls until `BSY` clears. The trigger, poll, cleanup, and timeout reset
|
||||
/// all stay in SRAM so neither an instruction fetch nor an IRQ vector can
|
||||
/// strand execution in Flash while `BSY` remains asserted.
|
||||
#[cfg_attr(
|
||||
target_os = "none",
|
||||
unsafe(link_section = ".data.kaptan_flash_operation")
|
||||
)]
|
||||
#[inline(never)]
|
||||
fn execute(&self, operation: F722FlashOperation) -> Result<(), F722FlashError> {
|
||||
#[cfg(target_os = "none")]
|
||||
let primask: u32;
|
||||
#[cfg(target_os = "none")]
|
||||
// SAFETY: these instructions only read and set the core interrupt mask.
|
||||
// The previous mask is restored after Flash is idle and relocked.
|
||||
unsafe {
|
||||
core::arch::asm!(
|
||||
"mrs {primask}, PRIMASK",
|
||||
"cpsid i",
|
||||
primask = out(reg) primask,
|
||||
options(nomem, nostack, preserves_flags),
|
||||
);
|
||||
}
|
||||
|
||||
match operation {
|
||||
F722FlashOperation::Erase { sector } => {
|
||||
// SAFETY: the caller resolved one of the two reserved sectors.
|
||||
self.flash.cr().modify(|_, write| unsafe {
|
||||
write
|
||||
.pg()
|
||||
.clear_bit()
|
||||
.ser()
|
||||
.set_bit()
|
||||
.snb()
|
||||
.bits(sector)
|
||||
.psize()
|
||||
.psize32()
|
||||
.strt()
|
||||
.start()
|
||||
});
|
||||
}
|
||||
F722FlashOperation::Program { address, word } => {
|
||||
self.flash
|
||||
.cr()
|
||||
.modify(|_, write| write.ser().clear_bit().psize().psize32().pg().set_bit());
|
||||
// SAFETY: the caller checked that this word is aligned and
|
||||
// contained in one reserved erased journal sector.
|
||||
unsafe { core::ptr::write_volatile(address as *mut u32, word) };
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "none")]
|
||||
// SAFETY: this operand-free barrier orders the erase trigger or
|
||||
// programming store before the status-register poll.
|
||||
unsafe {
|
||||
core::arch::asm!("dsb sy", options(nostack, preserves_flags));
|
||||
}
|
||||
let started = cortex_m::peripheral::DWT::cycle_count();
|
||||
while self.flash.sr().read().bsy().is_busy() {
|
||||
if remaining == 0 {
|
||||
return Err(F722FlashError::BusyTimeout);
|
||||
if cortex_m::peripheral::DWT::cycle_count().wrapping_sub(started)
|
||||
>= FLASH_OPERATION_TIMEOUT_CYCLES
|
||||
{
|
||||
reset_after_flash_timeout();
|
||||
}
|
||||
remaining -= 1;
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
Ok(())
|
||||
|
||||
let status = self.flash.sr().read();
|
||||
self.flash.cr().modify(|_, write| {
|
||||
write.pg().clear_bit();
|
||||
write.ser().clear_bit();
|
||||
write.lock().locked()
|
||||
});
|
||||
if status.eop().is_active() {
|
||||
self.flash.sr().write(|write| write.eop().clear());
|
||||
}
|
||||
|
||||
#[cfg(target_os = "none")]
|
||||
if primask & 1 == 0 {
|
||||
// SAFETY: Flash is idle and relocked, so restoring the caller's
|
||||
// enabled interrupt state cannot vector through a busy Flash bank.
|
||||
unsafe {
|
||||
core::arch::asm!("cpsie i", options(nomem, nostack, preserves_flags));
|
||||
}
|
||||
}
|
||||
|
||||
if status.bits() & FLASH_ERROR_FLAGS != 0 {
|
||||
Err(F722FlashError::Controller)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn unlock(&self) -> Result<(), F722FlashError> {
|
||||
self.wait_ready()?;
|
||||
if self.flash.sr().read().bsy().is_busy() {
|
||||
return Err(F722FlashError::Controller);
|
||||
}
|
||||
if self.flash.cr().read().lock().is_locked() {
|
||||
// SAFETY: these are the exact two full-width FPEC unlock keys from
|
||||
// RM0431, written in the required order while the controller is idle.
|
||||
@@ -316,23 +409,39 @@ impl F722Nor {
|
||||
.write(|write| unsafe { write.bits(FLASH_ERROR_FLAGS) });
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(&self) -> Result<(), F722FlashError> {
|
||||
self.wait_ready()?;
|
||||
let status = self.flash.sr().read();
|
||||
self.flash.cr().modify(|_, write| {
|
||||
write.pg().clear_bit();
|
||||
write.ser().clear_bit();
|
||||
write.lock().locked()
|
||||
});
|
||||
if status.bits() & FLASH_ERROR_FLAGS != 0 {
|
||||
return Err(F722FlashError::Controller);
|
||||
/// Requests a system reset without fetching code or data from embedded Flash.
|
||||
#[inline(always)]
|
||||
fn reset_after_flash_timeout() -> ! {
|
||||
#[cfg(target_os = "none")]
|
||||
{
|
||||
const AIRCR: *mut u32 = 0xe000_ed0c as *mut u32;
|
||||
const VECTKEY: u32 = 0x05fa << 16;
|
||||
const PRIGROUP: u32 = 0x7 << 8;
|
||||
const SYSRESETREQ: u32 = 1 << 2;
|
||||
|
||||
// SAFETY: this barrier has no operands or memory address. It orders the
|
||||
// preceding peripheral reads before the reset request.
|
||||
unsafe { core::arch::asm!("dsb sy", options(nostack, preserves_flags)) };
|
||||
// SAFETY: AIRCR is a word-aligned private-peripheral register. VECTKEY is
|
||||
// required for the write; retaining PRIGROUP avoids changing reset policy.
|
||||
unsafe {
|
||||
let priority_group = core::ptr::read_volatile(AIRCR) & PRIGROUP;
|
||||
core::ptr::write_volatile(AIRCR, VECTKEY | priority_group | SYSRESETREQ);
|
||||
}
|
||||
if status.eop().is_active() {
|
||||
self.flash.sr().write(|write| write.eop().clear());
|
||||
// SAFETY: this barrier ensures the reset request reaches the system
|
||||
// control block before the processor waits for reset.
|
||||
unsafe { core::arch::asm!("dsb sy", options(nostack, preserves_flags)) };
|
||||
loop {
|
||||
// SAFETY: `nop` has no operands, memory access, or observable state
|
||||
// beyond keeping the reset wait loop present.
|
||||
unsafe { core::arch::asm!("nop", options(nomem, nostack, preserves_flags)) };
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "none"))]
|
||||
panic!("flash timeout reset is an embedded-only path")
|
||||
}
|
||||
|
||||
impl DualSlotNor for F722Nor {
|
||||
@@ -356,25 +465,7 @@ impl DualSlotNor for F722Nor {
|
||||
fn erase_slot(&mut self, slot: usize) -> Result<(), Self::Error> {
|
||||
let sector = Self::sector(slot)?;
|
||||
self.prepare()?;
|
||||
// SAFETY: RM0431 assigns sectors 6 and 7 to the two reserved 128 KiB
|
||||
// ranges above. No application section is linked into either sector.
|
||||
self.flash.cr().modify(|_, write| unsafe {
|
||||
write
|
||||
.pg()
|
||||
.clear_bit()
|
||||
.ser()
|
||||
.set_bit()
|
||||
.snb()
|
||||
.bits(sector)
|
||||
.psize()
|
||||
.psize32()
|
||||
.strt()
|
||||
.start()
|
||||
});
|
||||
// RM0431 permits a DSB between asserting STRT and polling BSY so the
|
||||
// control-register write cannot be observed out of order.
|
||||
cortex_m::asm::dsb();
|
||||
self.finish()?;
|
||||
self.execute(F722FlashOperation::Erase { sector })?;
|
||||
let mut probe = [0_u8; 4];
|
||||
self.read(slot, 0, &mut probe)?;
|
||||
if probe != [0xff; 4] {
|
||||
@@ -389,16 +480,7 @@ impl DualSlotNor for F722Nor {
|
||||
}
|
||||
let address = Self::address(slot, offset, 4)?;
|
||||
self.prepare()?;
|
||||
self.flash
|
||||
.cr()
|
||||
.modify(|_, write| write.ser().clear_bit().psize().psize32().pg().set_bit());
|
||||
// SAFETY: the checked address is aligned and contained in a reserved
|
||||
// erased journal sector, and PG with x32 parallelism is active.
|
||||
unsafe { core::ptr::write_volatile(address as *mut u32, word) };
|
||||
// RM0431 requires the programming store to complete before BSY is
|
||||
// polled or the PG/control state is changed.
|
||||
cortex_m::asm::dsb();
|
||||
self.finish()?;
|
||||
self.execute(F722FlashOperation::Program { address, word })?;
|
||||
// SAFETY: the same checked flash word is readable after BSY clears.
|
||||
if unsafe { core::ptr::read_volatile(address as *const u32) } != word {
|
||||
return Err(F722FlashError::Verification);
|
||||
|
||||
@@ -183,6 +183,20 @@ fn ordinary_f722_image_has_direct_configuration_after_output_inhibition() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flash_busy_deadline_resets_entirely_from_sram() {
|
||||
let config = platform_source("src/f722/config.rs");
|
||||
assert!(config.contains("link_section = \".data.kaptan_flash_operation\""));
|
||||
assert!(config.contains("fn execute(&self, operation: F722FlashOperation)"));
|
||||
assert!(config.contains("\"mrs {primask}, PRIMASK\""));
|
||||
assert!(config.contains("\"cpsid i\""));
|
||||
assert!(config.contains("\"cpsie i\""));
|
||||
assert!(config.contains("fn reset_after_flash_timeout() -> !"));
|
||||
assert!(config.contains("SYSRESETREQ"));
|
||||
assert!(config.contains("core::arch::asm!(\"dsb sy\""));
|
||||
assert!(!config.contains("BusyTimeout"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usart_tx_dma_arms_each_stream_before_its_request() {
|
||||
let usart = platform_source("src/f722/peripherals/usart.rs");
|
||||
|
||||
Reference in new issue
Block a user