// Copyright (c) 2026 Rigless Labs.
// SPDX-License-Identifier: MIT OR Apache-2.0
//! The embedded flash memory interface of RM0090 rev 22 chapter 3, written against the `flash` layout.
//!
//! Chapter 3 gives every register twice: sections 3.9.1, 3.9.5, 3.9.7 and 3.9.9 are the
//! STM32F405xx/07xx and STM32F415xx/17xx's, and 3.9.2, 3.9.6, 3.9.8, 3.9.10 and 3.9.11 are the
//! STM32F42xxx and STM32F43xxx's, which is this SoC, the same trap chapter 6 sets against chapter 7
//! for the clock controller and 5.5 against 5.4 for the regulator. Every citation here is the second
//! of each pair.
//!
//! | Register or section | Behavior | Source | Test |
//! |---|---|---|---|
//! | Reset state | Zero wait states, nothing enabled, `CR` locked, the option registers as the option bytes left them | 3.5.1, 3.9.2, 3.9.8, 3.9.10, 3.9.11 | `reset_values_match_the_manual` |
//! | `ACR` `LATENCY` | Four bits on this family, corrected from the vendor's three | 3.9.2 | `a_latency_above_seven_reads_back_whole`, `the_corrected_field_is_what_the_part_declares` |
//! | `ACR` `LATENCY` held against the clock and the supply | Told, not refused, on the transition into a value short of table 12, whenever `ACR` is written or `HCLK` changes: the write has already landed by the time a model sees it, and no gate spans two instances' fields | 3.5.1, table 12 | `too_few_wait_states_for_the_clock_is_told`, `the_supply_narrows_the_table_the_same_way`, `the_column_edges_match_table_twelve` |
//! | `HCLK` past table 12's last row | Told: no wait-state count in the table reaches it, whatever `LATENCY` holds | 3.5.1, table 12 | `a_clock_past_the_tables_last_row_is_told` |
//! | `PRFTEN` set below 2.1 V | Told: 3.5.1 requires the prefetch buffer disabled below that rail | 3.5.1 | `prefetch_on_below_2_1_v_is_told` |
//! | `ACR` `PRFTEN`, `ICEN`, `DCEN` | Stored | 3.5.2 | `prften_icen_dcen_are_stored` |
//! | `ACR` `ICRST` | A write-only bit reads back its reset value, which is zero, so this model forces it back to zero on every write to `ACR` | 3.9.2, 1.1 | `icrst_always_reads_zero` |
//! | `ACR` `DCRST` | Stored while `DCEN` is clear. Section 3.9.2: "can be written only when the D cache is disabled", so a write while `DCEN` is set is held at its prior value | 3.9.2 | `dcrst_is_stored`, `dcrst_is_held_while_dcen_is_set` |
//! | `KEYR` unlock sequence | `KEY1` then `KEY2` clears `CR` `LOCK`. Anything else locks `CR` until reset and tells the session, once, on the word that does it | 3.6.1 | `the_unlock_sequence_clears_lock`, `a_wrong_key_locks_the_control_register_until_reset`, `a_wrong_first_key_locks_the_control_register_until_reset` |
//! | `CR` `LOCK` | Write-one-only, cleared only by the unlock sequence | 3.6.1, 3.9.8 | `the_unlock_sequence_clears_lock` |
//! | `CR` frozen while locked | Every other field of `CR` keeps the value it had. Section 3.6.1: "write is not allowed" until unlocked | 3.6.1 | `the_control_register_is_frozen_while_locked` |
//! | `CR`, `OPTCR`, `OPTCR1` written while `BSY` stands | Held and landed the instant `BSY` clears, the same write this model answers rather than one it ignored | 3.6.1, 3.9.10, 3.9.11 | `a_cr_write_made_while_busy_lands_when_the_operation_ends`, `an_optcr_write_made_while_busy_lands_when_the_operation_ends` |
//! | `CR` `SER`, `SNB`, `STRT` | Sector erase: `BSY` for the sector's own erase time by `PSIZE`, `STRT` clearing with it | 3.6.3, DS9405 rev 13 table 48 | `a_sector_erase_sets_and_clears_bsy`, `erase_time_follows_table_48_by_sector_size_and_psize` |
//! | `CR` `SNB` range | Sectors 0 to 11 and 16 to 27 raw are this part's twenty-four. The four gaps either side of that split are a write-protection error | 3.9.8, 3.7.4 | `an_invalid_sector_number_is_a_write_protection_error`, `a_bank_two_sector_erases_like_bank_one` |
//! | `CR` `MER`, `MER1`, `STRT` | Mass erase, either bank or both, timed by `PSIZE` alone since `tME` and `tBE` give the same figures. `STRT` with none of `SER`, `MER`, `MER1` set clears itself and starts nothing | 3.6.3, DS9405 rev 13 table 48 | `a_mass_erase_sets_and_clears_bsy`, `strt_with_nothing_selected_clears_itself_and_starts_nothing` |
//! | `CR` `SER` and `MER`/`MER1` together | 3.6.3's note gives a mass erase. 3.7.4's first bullet gives `WRPERR` for the same combination. This model follows 3.6.3, the sentence that describes the mechanism rather than the blanket rule | 3.6.3, 3.7.4 | `ser_with_mer_performs_a_mass_erase` |
//! | `CR` `PG`, `PSIZE` | Stored. `PSIZE` past what table 13 allows the supply is told when an erase starts, once per transition, since programming's own trigger is out of this model's reach, second table | 3.6.2, table 13 | `pg_and_psize_are_stored`, `an_erase_outside_table_thirteens_parallelism_is_told` |
//! | `HCLK` below 1 MHz at an erase | Told from `start_erase`: 3.6 requires at least 1 MHz for a program or erase operation | 3.6 | `an_erase_below_1_mhz_hclk_is_told` |
//! | `CR` `EOPIE`, `ERRIE` | Gate `EOP` and `OPERR` themselves, not only the interrupt, exactly as each bit's own page states | 3.9.6 | `eop_stands_only_while_eopie_is_set`, `operr_stands_only_while_errie_is_set` |
//! | `SR` `BSY` | Set for the operation's own duration, cleared at its end. A stale event id changes nothing, there being no cancel | 3.6.1, 3.6.3, 3.7.2 | `a_sector_erase_sets_and_clears_bsy`, `optstrt_sets_and_clears_bsy`, `a_stale_event_is_ignored` |
//! | `SR` `EOP` | Set behind `EOPIE` when `BSY` clears, correctly or not, since 3.6.6 states the interrupt condition is the operation ending either way, and 3.9.6's "successfully" would leave a firmware waiting on the `EOP` interrupt alone, with `ERRIE` clear, stuck on an erroring erase. Write-one-to-clear | 3.6.6, 3.7.2, 3.9.6 | `eop_stands_only_while_eopie_is_set`, `an_option_commit_also_raises_eop`, `eop_follows_3_6_6_and_stands_on_an_error_too` |
//! | `SR` `WRPERR` | Set on a write-protected sector or bank, an invalid `SNB`, or an option commit 3.7.5 cancels, whether or not `ERRIE` is set | 3.7.4, 3.7.5 | `an_invalid_sector_number_is_a_write_protection_error`, `nwrp_gates_erase_through_wrperr`, `unprotecting_without_rdp_leaving_level_1_cancels_the_commit` |
//! | `SR` `OPERR` | The meta flag: stands with any error this model can raise, behind `ERRIE` | 3.9.6 | `operr_stands_only_while_errie_is_set` |
//! | `SR` `RDERR` | The vendor's table omits it entirely, declared here since this family's own page gives it bit 8 | 3.9.6 | `rderr_is_declared` |
//! | `SR` write-one-to-clear | Every flag but `BSY`. The register-level correction covers `RDERR` too, since nothing else protects a declared field | 3.9.6 | `flags_clear_on_a_written_one` |
//! | Flash interrupt | One line, raised while `EOP` or `OPERR` stands and lowered the instant neither does, refreshed from every write this model answers | 3.6.6, table 14 | `the_interrupt_follows_eop_and_operr`, `clearing_eopie_lowers_the_line_from_a_cr_write`, `operr_raises_the_interrupt_when_errie_is_set` |
//! | `OPTKEYR` unlock sequence | `OPTKEY1` then `OPTKEY2` clears `OPTCR` `OPTLOCK`. Anything else locks the option registers until reset and tells the session, once | 3.7.2, 3.9.10 | `the_option_unlock_sequence_clears_optlock`, `a_wrong_option_key_locks_the_option_registers_until_reset` |
//! | `OPTCR` `OPTLOCK` | Write-one-only, cleared by hardware, section 3.9.10 bit 0 | 3.7.2, 3.9.10 | `the_option_unlock_sequence_clears_optlock` |
//! | `OPTCR`, `OPTCR1` frozen while locked | Every other field of both keeps its value while `OPTLOCK` stands | 3.7.2 | `the_option_registers_are_frozen_while_locked` |
//! | `OPTCR` `OPTSTRT` | Commits the option registers: `BSY` for the model's own programming time, and the committed words recorded for a reset to carry forward, unless 3.7.3's Level 2 or 3.7.5's cancel applies | 3.7.2, 3.9.10, 3.7.3, 3.7.5 | `optstrt_sets_and_clears_bsy`, `option_registers_survive_a_system_reset`, `level_2_freezes_a_commit`, `unprotecting_without_rdp_leaving_level_1_cancels_the_commit` |
//! | `OPTCR`, `OPTCR1` reset value | The option bytes as the last commit left them, kept across a system reset by [`Flash::keep_options`] rather than by copying whatever the registers held at the instant of the reset | 3.9.10, 3.9.11, 6.1.1 | `option_registers_survive_a_system_reset`, `a_reset_mid_commit_keeps_the_last_completed_words` |
//! | `OPTCR` `RDP` from Level 1 to Level 0 | Mass-erases the backup SRAM at the commit that lands it, table 18's footnote. The array's own half is left out | 3.7.3, table 18 | `unlocking_to_level_zero_is_told_apart_from_other_option_commits` |
//! | `OPTCR` `RDP`, `BOR_LEV`, `WDG_SW`, `nRST_STOP`, `nRST_STDBY`, `DB1M`, `SPRMOD`, `OPTCR`/`OPTCR1` `nWRP` | Stored and read back live. `nWRP` and `SPRMOD` gate an erase through `WRPERR` | 3.9.10, 3.9.11, 3.7.1, 3.7.4, 3.7.5 | `nwrp_gates_erase_through_wrperr`, `sprmod_flips_which_nwrp_value_protects`, `rdp_wdg_sw_nrst_stdby_and_db1m_are_stored` |
//!
//! **What the model leaves out, and why.**
//!
//! | Behavior | Source | Reason |
//! |---|---|---|
//! | Programming a word, and the true trigger of `PGAERR`, `PGPERR`, `PGSERR`, and `RDERR` | 3.6.4, 3.6.6, 3.7.5 | Every one of these is set by an access to the flash array itself, at `0x0800_0000`, which the core maps read-only. It drops a firmware's store there, so no model, this one included, is ever called for one, and a model's own write there through the host's guest memory is refused as the bus refuses one. Programming needs a way for a model to write the array, which is still to come |
//! | The array's own content, main memory and OTP alike: an erase does not set its sectors to all ones, a program does not deposit its bytes, and a locked OTP block does not refuse a second program with `WRPERR` | 3.6.3, 3.6.4, 3.8 | The same mapping, covering the OTP area (3.8, table 19) as well as `0x0800_0000`: both are the flash array the core maps read-only. `BSY`, `STRT`, `EOP`, and every error this model raises from register state alone are real. What a firmware reads back from the array or the OTP area afterward is what the run started with: the loaded image, and erased flash wherever no image fills it |
//! | `RDP`'s protection itself: JTAG, boot from RAM, and blocking erase or program under Level 1 or 2 | 3.7.3, table 18 | Debug gating is the core's and boot mode is the board's `BOOT`, neither a register this peripheral owns |
//! | `DB1M`'s effect on sector numbering | 3.4, table 7 | Table 7 is 1 Mbyte devices only. This part's memory map fixes a 2 Mbyte array, which table 6 always organizes the way `sector_of` reads it, so the bit is stored and changes nothing here |
//! | `BFB2` dual-bank boot | 3.7.1 | A machine's `boot` names what answers at the reset vector, and the system memory holds no bootloader here for `BFB2 1` to select, so a part committed with `BFB2 1` boots what its machine names regardless |
//! | Section 3.6.1's stall: a write to `CR`, `OPTCR` or `OPTCR1` while `BSY` stands "causes the AHB bus to stall" | 3.6.1, 3.9.10, 3.9.11 | Stalling the core mid-instruction is outside what the runtime can ask the engine to do. This model holds the write instead and lands it the instant `BSY` clears, which is the row above rather than a gap. The gap that remains is the stall itself, which no firmware sequence in the manual depends on observing |
//! | Section 3.6.1's bus error on a wrong unlock key | 3.6.1 | The write has already landed by the time a model is called, so there is nothing to refuse. The model tells the session instead of faulting the core. A local run prints it, and the link carries it to an engine in `Reached` |
//! | A write to `KEYR` or `OPTKEYR` once its register is already unlocked | 3.6.1, 3.9.3, 3.7.2, 3.9.4 | Neither page says what such a write does. This model treats it as nothing to do, which is the reading that cannot brick a sequence that already succeeded |
//! | 3.6 and 3.6.5: a read of one bank stalling while the other is erased or programmed, and the other bank answering meanwhile | 3.6, 3.6.5 | The same core-stall the row above already declines, and the array's own content besides |
//! | 3.6.2 and table 13's `VPP` column, x64 at 8 to 9 V for at most an hour | 3.6.2, table 13 | `VPP` is the `BOOT0` pad held at 8 to 9 V, DS9405 rev 13, which a programming fixture does and a running board doesn't. A machine has no input for it, so `PSIZE` x64 is told as past table 13 at any supply |
//! | 3.5.2 and 3.6.4: the ART's prefetch, its 64-line and 8-line caches, cache updates on a program, and stale lines after an erase until `ICRST`/`DCRST` | 3.5.2, 3.6.4 | `PRFTEN`, `ICEN` and `DCEN` qualify instruction and data timing, which the runtime doesn't model cycle by cycle |
//! | 3.6's undefined contents after a reset during an operation | 3.6 | A reset rebuilds the part fresh and drops whatever the old part had pending, which is one honest choice among the several the manual leaves open |
//! | 3.7.1, table 15: the option bytes readable as memory at `0x1FFF_C000` and `0x1FFE_C000` | 3.7.1, table 15 | `memory_map.rs`'s system region ends at `0x1FFF_8000` and never reaches `0x1FFE_C000` either, so both addresses are unmapped today. Answering them needs a memory region a model's live register state backs, which nothing in the runtime does yet |
//! | 3.9.10 `BOR_LEV` and 6.1.2: a supply below the selected level resets the part | 3.9.10, 6.1.2 | The supply is `Pwr`'s own input, and a reset it drives belongs beside `Pwr`'s existing brownout detector, not duplicated here |
//! | 3.7.1 and 6.1.1: `nRST_STOP`/`nRST_STDBY` resetting the part on Stop or Standby entry instead of entering it | 3.7.1, 6.1.1 | This model has no Stop or Standby to reset out of yet |
//! | `OPTCR` `WDG_SW` starting the independent watchdog automatically at reset when it selects the hardware option | 3.7.1, 3.9.10, 21.3.1 | Section 21.3.1: "If the 'Hardware watchdog' feature is enabled through the device option bits, the watchdog is automatically enabled at power-on." `WDG_SW` is stored here and kept across a system reset. Starting `IWDG` from it needs a model to act at the instant of a reset, which none can yet |
//! | RDP Level 1 to Level 0 mass-erasing the flash array half of table 18's footnote | 3.7.3, table 18 | The same array mapping. The backup SRAM half is modeled, below |
//! | ES0206 rev 24 2.2.3: unreachable instructions after `WFI`/`WFE` wake with the prefetch off, `LATENCY` above zero, a 4-byte-aligned `WFI`/`WFE`, and `DBG_SLEEP`/`DBG_STOP` set | ES0206 rev 24 | Applicable at every revision listed in table 3, `A` from rev A through rev B, including the 4/5/B this part reports. Its trigger is the core's instruction fetch pipeline racing the prefetch queue after a debug-held sleep, which needs `DBGMCU_CR` state this peripheral doesn't own and an effect on instruction fetch the runtime has no path to model |
//! | ES0206 rev 24 2.2.14: flash reads corrupt while `PA12` is a GPIO or alternate function, at 2.7 to 3.6 V, with bank 2 in use, on `STM32F42xxI` and `STM32F43xxI` (and `STM32F42xxG`/`STM32F43xxG` only with dual-bank enabled) | ES0206 rev 24 | Not applicable to the revisions this part reports (table 3 marks it `A` for revisions A, Y and 1 only, and the errata text says "fixed in silicon starting from revision 3"). Recorded rather than deleted, since the fact still holds for the revisions table 3 marks. `PA12`'s mode is `GPIOA`'s and reproducing the corruption needs the array's content regardless |
//! | ES0206 rev 24 2.2.15: the data cache may corrupt on a read from one bank while the other is written | ES0206 rev 24 | Applicable at every revision in table 3, `A` from rev A through rev B, including the 4/5/B this part reports. Its trigger is a read of the array during a write to it, the same unreachable access |
//!
//! **Where this model departs from the vendor's file.** `ACR` `LATENCY` is three bits in the SVD and
//! four in section 3.9.2. [`LATENCY`] and the `Builder::field` call in `memory_map.rs` carry the
//! correction. `SR` omits `RDERR` entirely, which section 3.9.6 gives bit 8. [`RDERR`] and a
//! `Builder::declare` call restore it. `CR` `LOCK` and `STRT`, and `OPTCR` `OPTLOCK` and `OPTSTRT`,
//! are plain read-write in the SVD where their own pages type them "write to 1 only, cleared by
//! hardware", so [`LOCK`], [`STRT`], [`OPTLOCK`] and [`OPTSTRT`] restate all four as write-one-to-set,
//! and the model clears them itself from inside its own effects. `SR`'s flags carry no write rule in
//! the SVD, where section 3.9.6 clears each but `BSY` on a written one, and a `Builder::write_modify`
//! call in `memory_map.rs` gives the register that rule.
//!
//! **Determinism.** `two_runs_of_one_script_are_the_same_run` drives two fresh instances through a
//! bricked `KEYR`, a `LATENCY` left short across a clock change, and a sector erase run to completion,
//! and checks `told`, the interrupt line and every register agree between the two.
use crate::device::{Device, DeviceContext, Level, RegisterFile};
use crate::soc::stm32f427::generated::flash::{Register, acr, cr, optcr, optcr1, sr};
use crate::soc::stm32f427::memory_map::SECTORS;
use crate::soc::{Access, FieldSpec, Layout, Width, WriteModify};
use crate::time::{EventId, VirtualNanos};
use std::num::NonZeroU32;
/// The flash interrupt, position zero of its instance's table. RM0090 rev 22 table 63 gives it line 4.
const IRQ: usize = 0;
/// How long the model takes to commit the option registers. RM0090 rev 22 section 3.7.2's note makes
/// a commit an erase of the user configuration sector then a program of every option byte, and
/// DS9405 rev 13 table 48 times neither for that sector, so this figure is the model's own with no
/// source behind it. It's far shorter than any erase the table does time: a firmware's own timeout
/// on `OPTSTRT` is never what ends the wait here.
const OPTION_PROGRAM_NANOS: u64 = 20_000;
/// RM0090 rev 22 section 3.6.1: `KEY1` then `KEY2` in `FLASH_KEYR` unlocks `FLASH_CR`.
const CR_KEY1: u32 = 0x4567_0123;
const CR_KEY2: u32 = 0xCDEF_89AB;
/// RM0090 rev 22 section 3.7.2: `OPTKEY1` then `OPTKEY2` in `FLASH_OPTKEYR` unlocks `FLASH_OPTCR`.
const OPT_KEY1: u32 = 0x0819_2A3B;
const OPT_KEY2: u32 = 0x4C5D_6E7F;
/// `LATENCY`, RM0090 rev 22 section 3.9.2.
pub static LATENCY: FieldSpec = FieldSpec::new("LATENCY", 0, 4, Access::ReadWrite);
/// `LOCK`, RM0090 rev 22 section 3.9.8: "Write to 1 only... cleared by hardware after detecting the
/// unlock sequence."
pub static LOCK: FieldSpec =
FieldSpec::new("LOCK", 31, 1, Access::ReadWrite).with_write(WriteModify::OneToSet);
/// `STRT`, RM0090 rev 22 section 3.9.8: "set only by software and cleared when the BSY bit is cleared."
pub static STRT: FieldSpec =
FieldSpec::new("STRT", 16, 1, Access::ReadWrite).with_write(WriteModify::OneToSet);
/// `OPTLOCK`, RM0090 rev 22 section 3.9.10, the same rule as [`LOCK`].
pub static OPTLOCK: FieldSpec =
FieldSpec::new("OPTLOCK", 0, 1, Access::ReadWrite).with_write(WriteModify::OneToSet);
/// `OPTSTRT`, RM0090 rev 22 section 3.9.10, the same rule as [`STRT`].
pub static OPTSTRT: FieldSpec =
FieldSpec::new("OPTSTRT", 1, 1, Access::ReadWrite).with_write(WriteModify::OneToSet);
/// `RDERR`, RM0090 rev 22 section 3.9.6 bit 8, which the vendor's table leaves out because it
/// transcribes the STM32F405's shorter `FLASH_SR`, section 3.9.5.
pub static RDERR: FieldSpec = FieldSpec::new("RDERR", 8, 1, Access::ReadWrite);
/// Every field this model declares because the vendor's file omits it. See
/// [`super::rcc::DECLARED`](super::rcc).
pub static DECLARED: &[&FieldSpec] = &[&RDERR];
/// Table 12, RM0090 rev 22 section 3.5.1: the greatest `HCLK` in hertz each wait-state count reaches,
/// one row per voltage range column, in ascending wait states.
const WAIT_STATE_CEILING_HZ: [[u64; 9]; 4] = [
// 2.7 to 3.6 V.
[
30_000_000,
60_000_000,
90_000_000,
120_000_000,
150_000_000,
180_000_000,
180_000_000,
180_000_000,
180_000_000,
],
// 2.4 to 2.7 V.
[
24_000_000,
48_000_000,
72_000_000,
96_000_000,
120_000_000,
144_000_000,
168_000_000,
180_000_000,
180_000_000,
],
// 2.1 to 2.4 V.
[
22_000_000,
44_000_000,
66_000_000,
88_000_000,
110_000_000,
132_000_000,
154_000_000,
176_000_000,
180_000_000,
],
// 1.8 to 2.1 V, prefetch off.
[
20_000_000,
40_000_000,
60_000_000,
80_000_000,
100_000_000,
120_000_000,
140_000_000,
160_000_000,
168_000_000,
],
];
/// The column of [`WAIT_STATE_CEILING_HZ`] a supply falls in. RM0090 rev 22 section 3.5.1's four
/// ranges, each named by its lower bound.
fn column(supply_mv: NonZeroU32) -> usize {
match supply_mv.get() {
2_700.. => 0,
2_400..2_700 => 1,
2_100..2_400 => 2,
_ => 3,
}
}
/// The fewest wait states table 12 allows at `hclk_hz` on `supply_mv`, or nothing where the supply is
/// unknown or `hclk_hz` is past the column's last row.
fn minimum_wait_states(hclk_hz: u64, supply_mv: Option<NonZeroU32>) -> Option<u32> {
let ceilings = WAIT_STATE_CEILING_HZ[column(supply_mv?)];
if hclk_hz > ceilings[8] {
return None;
}
Some(u32::try_from(ceilings.partition_point(|&ceiling| ceiling < hclk_hz)).expect("nine rows"))
}
/// Table 13, RM0090 rev 22 section 3.6.2: the greatest `PSIZE` the supply allows, without the external
/// `VPP` column, DS9405 rev 13's name for the `BOOT0` pad, which only a programming fixture drives. `pdftotext
/// -layout` splits table 13's merged x16 cell across the 2.4-2.7 V and 2.1-2.4 V columns into two. DS9405
/// rev 13 table 18 ("16-bit erase and program operations" at both 2.1-2.4 V and 2.4-2.7 V) and table 48
/// (`Vprog` 16-bit, 2.1 to 3.6 V) agree it is one cell.
fn max_psize(supply_mv: NonZeroU32) -> u64 {
match column(supply_mv) {
0 => 0b10,
1 | 2 => 0b01,
_ => 0b00,
}
}
/// The sector `SNB` names, RM0090 rev 22 section 3.9.8: 0 to 11 raw are sectors 0 to 11, 16 to 27 raw
/// are sectors 12 to 23, and the two four-value gaps either side are not allowed.
fn sector_of(raw: u32) -> Option<u32> {
match raw {
0..=11 => Some(raw),
16..=27 => Some(raw - 4),
_ => None,
}
}
/// The size in bytes of `sector`, RM0090 rev 22 table 6, which the memory map's flash sectors are.
fn sector_bytes(sector: u32) -> u64 {
let mut left = sector;
for run in &SECTORS {
if left < run.count.get() {
return run.size.get();
}
left -= run.count.get();
}
unreachable!("sector {sector} is past table 6's twenty-four, which sector_of refuses")
}
/// DS9405 rev 13 table 48's typical sector erase time in milliseconds, by size (16, 64, 128 Kbytes)
/// and `PSIZE` (x8, x16, x32 in that order, `PSIZE`'s own encoding).
const SECTOR_ERASE_MS: [[u64; 3]; 3] = [[400, 300, 250], [1_200, 700, 550], [2_000, 1_300, 1_000]];
/// DS9405 rev 13 table 48's typical bank or mass erase time in milliseconds, by `PSIZE`: `tME` and
/// `tBE` give the same figures, so this model does not distinguish a mass erase from a single bank's.
const BANK_ERASE_MS: [u64; 3] = [16_000, 11_000, 8_000];
/// `PSIZE`'s own two bits as an index into a table's x8/x16/x32 columns, clamped past x32 since this
/// model has no `VPP` pin for x64.
fn psize_index(cr_value: u64) -> usize {
usize::try_from(cr::PSIZE.get(cr_value))
.expect("two bits")
.min(2)
}
/// DS9405 rev 13 table 48's typical time for erasing `sector`, in nanoseconds, at `psize`. This model
/// is not kinder than the part: a firmware's own erase deadline or watchdog has to survive this wait
/// the way it would survive the part's.
fn sector_erase_nanos(sector: u32, cr_value: u64) -> u64 {
let row = match sector_bytes(sector) {
n if n <= 16 << 10 => 0,
n if n <= 64 << 10 => 1,
_ => 2,
};
SECTOR_ERASE_MS[row][psize_index(cr_value)] * 1_000_000
}
/// DS9405 rev 13 table 48's typical time for a bank or mass erase, in nanoseconds, at `psize`.
fn bank_erase_nanos(cr_value: u64) -> u64 {
BANK_ERASE_MS[psize_index(cr_value)] * 1_000_000
}
/// Whether `sector` is protected against erase, RM0090 rev 22 sections 3.7.4 and 3.7.5: `nWRPi` clear
/// blocks it unless `SPRMOD` is set, in which case `nWRPi` set does instead.
fn protected(sector: u32, registers: &RegisterFile) -> bool {
let sprmod = optcr::SPRMOD.is_set(registers.get(Register::Optcr));
let bit_set = if sector <= 11 {
(optcr::NWRP.get(registers.get(Register::Optcr)) >> sector) & 1 == 1
} else {
(optcr1::NWRP.get(registers.get(Register::Optcr1)) >> (sector - 12)) & 1 == 1
};
sprmod == bit_set
}
/// The `nWRP`/`nWRP1` bits across both registers, as one word, sector 0 at bit 0 through sector 23 at
/// bit 23: what 3.7.5's cancel rule compares before and after a commit.
fn all_nwrp(optcr: u64, optcr1: u64) -> u32 {
let low = u32::try_from(optcr::NWRP.get(optcr)).expect("twelve bits");
let high = u32::try_from(optcr1::NWRP.get(optcr1)).expect("twelve bits");
low | (high << 12)
}
/// `RDP`'s protection level, RM0090 rev 22 section 3.7.3: `0xAA` is Level 0, `0xCC` is Level 2, and
/// every other value is Level 1.
fn rdp_level(optcr: u64) -> u8 {
match optcr::RDP.get(optcr) {
0xAA => 0,
0xCC => 2,
_ => 1,
}
}
/// What the model has scheduled the completion of.
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
enum Operation {
Erase,
Option,
}
/// Progress through a two-word unlock sequence, RM0090 rev 22 sections 3.6.1 and 3.7.2: `KEY1` then
/// `KEY2` in order, and anything else locks the sequence's register until the next reset.
#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
struct KeyLock {
expecting_second: bool,
bricked: bool,
}
impl KeyLock {
/// Whether `written` unlocks the sequence.
fn take(&mut self, written: u64, key1: u32, key2: u32) -> bool {
if self.bricked {
return false;
}
if self.expecting_second {
self.expecting_second = false;
if written == u64::from(key2) {
return true;
}
} else if written == u64::from(key1) {
self.expecting_second = true;
return false;
}
self.bricked = true;
false
}
}
/// What a write must not change unless it is the write doing the changing: a model sees the value
/// already landed and not the one before it, so freezing a register while locked, or holding a write
/// made while `BSY` stands, needs the instant before this write to restore to.
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
struct Shadow {
acr: u64,
cr: u64,
optcr: u64,
optcr1: u64,
}
impl Shadow {
fn from_registers(registers: &RegisterFile) -> Self {
Self {
acr: registers.get(Register::Acr),
cr: registers.get(Register::Cr),
optcr: registers.get(Register::Optcr),
optcr1: registers.get(Register::Optcr1),
}
}
}
/// The option words as the last completed commit left them, RM0090 rev 22 section 3.9.10's "loaded
/// with values from flash memory at reset release", carried across a reset rather than whatever
/// `OPTCR` and `OPTCR1` hold at the instant it happens, which may be an uncommitted edit or a commit
/// still in flight.
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
struct Committed {
optcr: u64,
optcr1: u64,
}
/// A write to `CR`, `OPTCR` or `OPTCR1` that arrived while `BSY` stood, held for landing when the
/// operation ends: RM0090 rev 22 sections 3.9.10 and 3.9.11 give both option registers the same "no
/// wait state when no flash memory operation is ongoing" access line as `CR`, and 3.6.1's note makes a
/// write during that wait a stall that lands once `BSY` clears, not one silently dropped.
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
struct Held {
#[serde(with = "row")]
register: Register,
value: u64,
}
/// Serde for the register a held write names, as its row in the table, since the generated tables
/// carry no serde of their own.
mod row {
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use super::Register;
use crate::soc::Layout;
#[expect(
clippy::trivially_copy_pass_by_ref,
reason = "serde's `with` hands the field over by reference"
)]
pub(super) fn serialize<S: Serializer>(
register: &Register,
serializer: S,
) -> Result<S::Ok, S::Error> {
register.index().serialize(serializer)
}
pub(super) fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Register, D::Error> {
let index = usize::deserialize(deserializer)?;
Register::from_index(index).ok_or_else(|| {
serde::de::Error::custom(format_args!("the interface has no register at row {index}"))
})
}
}
/// The embedded flash memory interface of one STM32F42xxx, RM0090 rev 22 chapter 3.
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[expect(
clippy::struct_excessive_bools,
reason = "five conditions each told on its own transition, not a state machine over one another"
)]
pub struct Flash {
hclk_hz: u64,
supply_mv: Option<NonZeroU32>,
cr_key: KeyLock,
opt_key: KeyLock,
pending: Option<(EventId, Operation)>,
held: Option<Held>,
shadow: Shadow,
committed: Committed,
/// The level last driven, so the line is reported only on a change, never driven a second time
/// for landing on the same level it already stood at.
interrupt: Level,
/// Whether the session was already told the latency is short of table 12, so a firmware that
/// leaves it short is told once rather than on every access.
told_latency: bool,
/// Whether the session was already told `HCLK` is past table 12's last row.
told_out_of_range: bool,
/// Whether the session was already told the prefetch buffer is on below 2.1 V.
told_prefetch: bool,
/// Whether the session was already told an erase started with `PSIZE` past what the supply allows.
told_parallelism: bool,
/// Whether the session was already told an erase started with `HCLK` below 1 MHz.
told_hclk_low: bool,
}
impl Flash {
/// The interface fresh out of reset, on the clock the tree drives every bus at until a firmware
/// touches it, with no supply until a board sets one.
///
/// `shadow` and `committed` are built here from each register's own
/// [`RegisterSpec::reset`](crate::soc::RegisterSpec::reset) rather than from a live register
/// file, because none exists until after this call returns: a model is constructed first and
/// wired into its instance's file second. By the time [`Device::write`] gives this model a
/// [`RegisterFile`], that file already holds what the current write landed (its own doc says
/// so), so there is no call inside a `Device` method that ever sees the state before a first
/// write the way this constructor does. `Default` is not implemented for either: this is the
/// one place either is built.
#[must_use]
pub fn with_clock(hclk_hz: u64) -> Self {
Self {
hclk_hz,
supply_mv: None,
cr_key: KeyLock::default(),
opt_key: KeyLock::default(),
pending: None,
held: None,
shadow: Shadow {
acr: Register::Acr.spec().reset,
cr: Register::Cr.spec().reset,
optcr: Register::Optcr.spec().reset,
optcr1: Register::Optcr1.spec().reset,
},
committed: Committed {
optcr: Register::Optcr.spec().reset,
optcr1: Register::Optcr1.spec().reset,
},
interrupt: Level::Low,
told_latency: false,
told_out_of_range: false,
told_prefetch: false,
told_parallelism: false,
told_hclk_low: false,
}
}
/// The board's supply rail, in millivolts: table 12's other axis, alongside `HCLK`, where nothing
/// having set one yet is `column` not having an answer, rather than a violation.
pub fn set_supply(&mut self, millivolts: NonZeroU32) {
self.supply_mv = Some(millivolts);
}
/// The supply this instance was last told, for a system reset to carry to the part it rebuilds.
#[must_use]
pub const fn supply(&self) -> Option<NonZeroU32> {
self.supply_mv
}
/// A system reset carrying the option words the last completed commit left, RM0090 rev 22 section
/// 3.9.10, rather than whatever `OPTCR` and `OPTCR1` held at the instant of the reset: `OPTLOCK`
/// comes back set and `OPTSTRT` comes back clear, since a reset is never mid-commit.
pub fn keep_options(&mut self, optcr: u64, optcr1: u64, registers: &mut RegisterFile) {
let optcr = OPTLOCK.set(OPTSTRT.set(optcr, 0), 1);
registers.set(Register::Optcr, optcr);
registers.set(Register::Optcr1, optcr1);
self.committed = Committed { optcr, optcr1 };
self.shadow = Shadow::from_registers(registers);
}
/// The option words the last completed commit left, for a system reset to carry to the part it
/// rebuilds through [`Flash::keep_options`].
#[must_use]
pub const fn committed_options(&self) -> (u64, u64) {
(self.committed.optcr, self.committed.optcr1)
}
}
impl Device for Flash {
type Register = Register;
fn write(
&mut self,
register: Register,
written: u64,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
match register {
Register::Acr => self.write_acr(registers, context),
Register::Keyr => self.write_keyr(written, registers, context),
Register::Optkeyr => self.write_optkeyr(written, registers, context),
Register::Sr => self.refresh_interrupt(registers, context),
Register::Cr => self.write_cr(registers, context),
Register::Optcr | Register::Optcr1 => self.write_optcr(register, registers, context),
}
self.shadow = Shadow::from_registers(registers);
}
fn on_event(
&mut self,
event: EventId,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
let Some((expected, operation)) = self.pending else {
return;
};
if expected != event {
return;
}
self.pending = None;
let sr_value = sr::BSY.set(registers.get(Register::Sr), 0);
let sr_value = if cr::EOPIE.is_set(registers.get(Register::Cr)) {
sr::EOP.set(sr_value, 1)
} else {
sr_value
};
registers.set(Register::Sr, sr_value);
match operation {
Operation::Erase => {
let cr_value = STRT.set(registers.get(Register::Cr), 0);
registers.set(Register::Cr, cr_value);
}
Operation::Option => {
let optcr_value = OPTSTRT.set(registers.get(Register::Optcr), 0);
registers.set(Register::Optcr, optcr_value);
self.committed = Committed {
optcr: registers.get(Register::Optcr),
optcr1: registers.get(Register::Optcr1),
};
}
}
self.refresh_interrupt(registers, context);
self.shadow = Shadow::from_registers(registers);
self.land_held(registers, context);
}
fn on_clock(&mut self, hz: u64, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
self.hclk_hz = hz;
self.check_latency(registers, context);
}
}
impl Flash {
/// A write to `CR`, `OPTCR` or `OPTCR1` held for `BSY`, applied as if it had just arrived: RM0090
/// rev 22 sections 3.9.10, 3.9.11 and 3.6.1's stall note.
fn land_held(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
let Some(held) = self.held.take() else {
return;
};
registers.set(held.register, held.value);
match held.register {
Register::Cr => self.write_cr(registers, context),
Register::Optcr | Register::Optcr1 => {
self.write_optcr(held.register, registers, context);
}
_ => {}
}
}
/// `ACR`: force `ICRST` back to zero, hold `DCRST` while `DCEN` stands, section 3.9.2, and hold
/// `LATENCY` to what the clock and the supply need.
fn write_acr(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
if acr::DCEN.is_set(self.shadow.acr) {
let held = acr::DCRST.set(
registers.get(Register::Acr),
u64::from(acr::DCRST.is_set(self.shadow.acr)),
);
registers.set(Register::Acr, held);
}
let value = acr::ICRST.set(registers.get(Register::Acr), 0);
registers.set(Register::Acr, value);
self.check_latency(registers, context);
}
/// RM0090 rev 22 section 3.5.1 table 12: told, not refused, since the write has already landed
/// and the supply is another instance's board input rather than a bit this model can gate on.
/// Told once, on the transition into the condition, the way an interrupt line is driven once per
/// edge.
fn check_latency(&mut self, registers: &RegisterFile, context: &mut DeviceContext<'_>) {
let minimum = minimum_wait_states(self.hclk_hz, self.supply_mv);
let short = minimum.is_some_and(|minimum| {
let programmed =
u32::try_from(LATENCY.get(registers.get(Register::Acr))).expect("four bits");
programmed < minimum
});
if short && !self.told_latency {
context.tell(
Register::Acr.spec().name,
"RM0090 rev 22 section 3.5.1 table 12: LATENCY is short of what HCLK and the supply require",
);
}
self.told_latency = short;
let out_of_range = self.supply_mv.is_some() && minimum.is_none();
if out_of_range && !self.told_out_of_range {
context.tell(
Register::Acr.spec().name,
"RM0090 rev 22 section 3.5.1 table 12: HCLK is past this column's highest wait-state row",
);
}
self.told_out_of_range = out_of_range;
let prefetch_off_required = self.supply_mv.is_some_and(|mv| mv.get() < 2_100);
let prefetch_on = acr::PRFTEN.is_set(registers.get(Register::Acr));
let should_tell_prefetch = prefetch_off_required && prefetch_on;
if should_tell_prefetch && !self.told_prefetch {
context.tell(
Register::Acr.spec().name,
"RM0090 rev 22 section 3.5.1: the prefetch buffer must be disabled when the supply voltage is below 2.1 V",
);
}
self.told_prefetch = should_tell_prefetch;
}
/// `KEYR`: RM0090 rev 22 section 3.6.1's sequence, only meaningful while `CR` is locked.
fn write_keyr(
&mut self,
written: u64,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
if !LOCK.is_set(registers.get(Register::Cr)) {
return;
}
let was_bricked = self.cr_key.bricked;
if self.cr_key.take(written, CR_KEY1, CR_KEY2) {
let value = LOCK.set(registers.get(Register::Cr), 0);
registers.set(Register::Cr, value);
} else if self.cr_key.bricked && !was_bricked {
context.tell(
Register::Keyr.spec().name,
"RM0090 rev 22 section 3.6.1: a wrong unlock sequence locks the control register until reset",
);
}
}
/// `OPTKEYR`: the same sequence, section 3.7.2, over `OPTCR`'s `OPTLOCK`.
fn write_optkeyr(
&mut self,
written: u64,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
if !OPTLOCK.is_set(registers.get(Register::Optcr)) {
return;
}
let was_bricked = self.opt_key.bricked;
if self.opt_key.take(written, OPT_KEY1, OPT_KEY2) {
let value = OPTLOCK.set(registers.get(Register::Optcr), 0);
registers.set(Register::Optcr, value);
} else if self.opt_key.bricked && !was_bricked {
context.tell(
Register::Optkeyr.spec().name,
"RM0090 rev 22 section 3.9.10: a wrong unlock sequence locks the option registers until reset",
);
}
}
/// `CR`: frozen while locked, section 3.6.1. A write made while `BSY` stands is held rather than
/// dropped, sections 3.9.10, 3.9.11 and 3.6.1's stall note, and a rising `STRT` starts an erase.
fn write_cr(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
if LOCK.is_set(self.shadow.cr) {
registers.restore(Register::Cr, !LOCK.mask(), self.shadow.cr);
} else if sr::BSY.is_set(registers.get(Register::Sr)) {
self.held = Some(Held {
register: Register::Cr,
value: registers.get(Register::Cr),
});
registers.restore(Register::Cr, !LOCK.mask(), self.shadow.cr);
} else {
let value = registers.get(Register::Cr);
if STRT.is_set(value) && !STRT.is_set(self.shadow.cr) {
self.start_erase(value, registers, context);
}
}
self.refresh_interrupt(registers, context);
}
/// The erase section 3.6.3 describes: mass erase where either `MER` bit is set, sector erase
/// otherwise, each refused with `WRPERR` under section 3.7.4's own conditions and timed by DS9405
/// rev 13 table 48. `STRT` set with none of `SER`, `MER`, `MER1` clears itself, section 3.6.3's
/// "unpredictable behavior... should be forbidden" left as starting nothing rather than choosing
/// among the behaviors it forbids relying on.
fn start_erase(
&mut self,
cr_value: u64,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
let nanos = if cr::MER.is_set(cr_value) || cr::MER1.is_set(cr_value) {
let refused = (cr::MER.is_set(cr_value)
&& (0..=11).any(|sector| protected(sector, registers)))
|| (cr::MER1.is_set(cr_value)
&& (12..=23).any(|sector| protected(sector, registers)));
if refused {
self.raise_error(sr::WRPERR, registers, context);
return;
}
bank_erase_nanos(cr_value)
} else if cr::SER.is_set(cr_value) {
let raw = u32::try_from(cr::SNB.get(cr_value)).expect("five bits");
let Some(sector) = sector_of(raw) else {
self.raise_error(sr::WRPERR, registers, context);
return;
};
if protected(sector, registers) {
self.raise_error(sr::WRPERR, registers, context);
return;
}
sector_erase_nanos(sector, cr_value)
} else {
let cleared = STRT.set(registers.get(Register::Cr), 0);
registers.set(Register::Cr, cleared);
return;
};
self.check_parallelism(cr_value, context);
let hclk_low = self.hclk_hz < 1_000_000;
if hclk_low && !self.told_hclk_low {
context.tell(
Register::Cr.spec().name,
"RM0090 rev 22 section 3.6: HCLK is below the 1 MHz an erase or program operation requires",
);
}
self.told_hclk_low = hclk_low;
self.begin(Operation::Erase, nanos, registers, context);
}
/// RM0090 rev 22 section 3.6.2 and table 13: `PSIZE` must be set correctly before any program or
/// erase operation, to what the supply allows. Told once per transition, the way `check_latency`
/// tells rather than refuses.
fn check_parallelism(&mut self, cr_value: u64, context: &mut DeviceContext<'_>) {
let Some(supply_mv) = self.supply_mv else {
self.told_parallelism = false;
return;
};
let short = cr::PSIZE.get(cr_value) > max_psize(supply_mv);
if short && !self.told_parallelism {
context.tell(
Register::Cr.spec().name,
"RM0090 rev 22 section 3.6.2 table 13: PSIZE exceeds what the supply allows",
);
}
self.told_parallelism = short;
}
/// `OPTCR`, `OPTCR1`: frozen while locked, section 3.7.2. A write made while `BSY` stands is held
/// rather than dropped, sections 3.9.10, 3.9.11 and 3.6.1's stall note. A rising `OPTSTRT` commits
/// them, unless section 3.7.3's Level 2 freezes the option bytes outright or section 3.7.5's cancel
/// applies, each of which restores the last completed commit instead of starting one.
fn write_optcr(
&mut self,
register: Register,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
if OPTLOCK.is_set(self.shadow.optcr) {
registers.restore(Register::Optcr, !OPTLOCK.mask(), self.shadow.optcr);
registers.restore(Register::Optcr1, u64::MAX, self.shadow.optcr1);
return;
}
if sr::BSY.is_set(registers.get(Register::Sr)) {
self.held = Some(Held {
register,
value: registers.get(register),
});
registers.restore(Register::Optcr, !OPTLOCK.mask(), self.shadow.optcr);
registers.restore(Register::Optcr1, u64::MAX, self.shadow.optcr1);
return;
}
let value = registers.get(Register::Optcr);
if !OPTSTRT.is_set(value) || OPTSTRT.is_set(self.shadow.optcr) {
return;
}
if rdp_level(self.committed.optcr) == 2 {
registers.restore(Register::Optcr, u64::MAX, self.committed.optcr);
registers.restore(Register::Optcr1, u64::MAX, self.committed.optcr1);
return;
}
let optcr1 = registers.get(Register::Optcr1);
let unlocks_to_zero = rdp_level(self.committed.optcr) == 1 && rdp_level(value) == 0;
// 3.7.5: the restricted "unprotection" is PCROP's, not plain write-protection's. A bit
// clearing under SPRMOD 0 is a sector gaining write protection, the ordinary case every
// other test here exercises. It is only a sector losing PCROP's read-and-write protection,
// under SPRMOD 1, that this cancel rule reaches.
let was_pcrop = optcr::SPRMOD.is_set(self.committed.optcr);
let was_wrp = all_nwrp(self.committed.optcr, self.committed.optcr1);
let now_wrp = all_nwrp(value, optcr1);
let unprotects = was_pcrop && ((!optcr::SPRMOD.is_set(value)) || (was_wrp & !now_wrp != 0));
if unprotects && !unlocks_to_zero {
registers.restore(Register::Optcr, u64::MAX, self.committed.optcr);
registers.restore(Register::Optcr1, u64::MAX, self.committed.optcr1);
self.raise_error(sr::WRPERR, registers, context);
return;
}
self.begin(Operation::Option, OPTION_PROGRAM_NANOS, registers, context);
if unlocks_to_zero {
Self::clear_backup_sram(context);
}
}
/// RM0090 rev 22 section 3.7.3 and table 18's footnote: the main flash memory and the backup SRAM
/// are mass-erased only on the commit that moves `RDP` from Level 1 to Level 0. The array's own
/// half is left out. This clears the backup SRAM at `0x4002_4000`, which the runtime does map.
/// The manual does not state the erased value for the SRAM half the way 3.6.4's note implies
/// all ones for flash, so this model clears it to zero.
fn clear_backup_sram(context: &mut DeviceContext<'_>) {
const BACKUP_SRAM: u64 = 0x4002_4000;
const BACKUP_SRAM_WORDS: u64 = (4 << 10) / 4;
for word in 0..BACKUP_SRAM_WORDS {
context.write_to(BACKUP_SRAM + word * 4, Width::W32, 0, word);
}
}
/// Raises `BSY` and schedules the operation's own end.
fn begin(
&mut self,
operation: Operation,
nanos: u64,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
let value = sr::BSY.set(registers.get(Register::Sr), 1);
registers.set(Register::Sr, value);
let at = VirtualNanos(context.now().0 + nanos);
self.pending = Some((context.schedule(at), operation));
}
/// Sets `flag`, `OPERR` behind `ERRIE`, and `EOP` behind `EOPIE`, RM0090 rev 22 sections 3.7.4 and
/// 3.9.6, then clears the `STRT` that asked for the operation this refuses: nothing was started, so
/// nothing is left `BSY`. `EOP` follows 3.6.6's "operation completed, correctly or not" rather than
/// 3.9.6's "successfully" alone, since a firmware using 3.6.5's "or use the EOP interrupt" with
/// `ERRIE` clear would otherwise wait forever on an erase this model refuses.
fn raise_error(
&mut self,
flag: &FieldSpec,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
let mut value = flag.set(registers.get(Register::Sr), 1);
if cr::ERRIE.is_set(registers.get(Register::Cr)) {
value = sr::OPERR.set(value, 1);
}
if cr::EOPIE.is_set(registers.get(Register::Cr)) {
value = sr::EOP.set(value, 1);
}
registers.set(Register::Sr, value);
let cr_value = STRT.set(registers.get(Register::Cr), 0);
registers.set(Register::Cr, cr_value);
self.refresh_interrupt(registers, context);
}
/// RM0090 rev 22 table 14: the line stands while `EOP` or `OPERR` does and lowers the instant
/// neither does, which is what the register file's own write-one-to-clear leaves to this model to
/// notice.
fn refresh_interrupt(&mut self, registers: &RegisterFile, context: &mut DeviceContext<'_>) {
let sr_value = registers.get(Register::Sr);
let cr_value = registers.get(Register::Cr);
let active = (sr::EOP.is_set(sr_value) && cr::EOPIE.is_set(cr_value))
|| (sr::OPERR.is_set(sr_value) && cr::ERRIE.is_set(cr_value));
let wanted = Level::from(active);
if wanted != self.interrupt {
self.interrupt = wanted;
context.interrupt(IRQ, wanted);
}
}
}