// Copyright (c) 2026 Rigless Labs.
// SPDX-License-Identifier: MIT OR Apache-2.0
//! The extended interrupt and event controller of RM0433 rev 9 chapter 20, written against the `exti`
//! layout, for the Cortex-M7 that this part has. Every citation is RM0433 rev 9 unless it names another
//! document.
//!
//! The register pages name the processor's registers `EXTI_CPUIMRx`, `EXTI_CPUEMRx` and `EXTI_CPUPRx`,
//! and Table 149 calls the first two `EXTI_C1IMR` and `EXTI_C1EMR`. This part has one core, so it has
//! the first set alone.
//!
//! **What the model handles and what falls to the register file.** Every register of the chapter has an
//! arm in [`Device::write`], and the model overrides no read, since the register file answers each from
//! the values the model keeps in it. The file settles the stores, the resets and the write-one-to-clear
//! of the three pending registers once the part has corrected them. The model has the rest: the edge
//! detection of the sixteen GPIO lines, the pending bits, the interrupts, the software trigger, and the
//! clearing of a pending bit by a change of trigger. Four registers the vendor's file types read-only
//! hold values the firmware writes, `EXTI_CPUIMR3`, `EXTI_CPUEMR3`, `EXTI_CPUPR2` and `EXTI_CPUPR3`, so
//! the model applies each of their writes itself.
//!
//! **How a GPIO pin reaches an EXTI line.** Section 20.4: for the sixteen GPIO event inputs "the
//! associated IOPORT pin has to be selected in the SYSCFG register `SYSCFG_EXTICRn`". Two of the
//! runtime's seams join the three instances, and no model reaches another.
//!
//! 1. Each port announces the level of its pins as request lines, request `n` being pin `n`. The part
//! names this instance a reader with `reads_requests("EXTI")`, so it hears every change. It
//! recognizes a port by [`PORTS`] and keeps the last level of each of the 176 pins, since a request
//! names the new level alone.
//! 2. An edge that a trigger register selects is routed by `SYSCFG_EXTICRn`, which this instance reads
//! with a requested access at the instant of the edge. Line `n` takes pin `n` of the port its field
//! names, so an edge on pin `n` of another port is not the line's. The register stays in the SYSCFG,
//! where the firmware wrote it, so there's no second copy to go stale, and the read is made only
//! when a pin changes under a selected edge.
//!
//! The part has three things to give for this to work: the SYSCFG instance's base to [`Exti::new`], the
//! ports in the order of [`PORTS`], and `reads_requests("EXTI")`.
//!
//! | Register or section | Behavior | Source | Test |
//! |---|---|---|---|
//! | Reset state | Every register at its manual value: the trigger, software, D3 and event mask registers zero, the pending registers "undefined" on the page and zero here, and the interrupt masks with the direct lines unmasked, `0xFFC0_0000`, `0xFFF5_FFFF` and `0x018B_FFFF`. The file gives the last two zero, corrected in `memory_map.rs` | 20.6.1 to 20.6.27 | `every_register_comes_out_of_reset_as_the_manual_gives_it` |
//! | `EXTI_RTSR1` to `EXTI_RTSR3`, `EXTI_FTSR1` to `EXTI_FTSR3` | Select the rising and the falling edge, or both, of each configurable line. A line is enabled by enabling at least one | 20.3.1, 20.5, 20.6.1, 20.6.2, 20.6.7, 20.6.8, 20.6.13, 20.6.14 | `a_rising_edge_pends_its_line_and_raises_its_vector`, `a_falling_edge_pends_its_line`, `both_edges_pend_a_line_that_selects_both`, `a_line_with_no_edge_selected_pends_nothing` |
//! | The GPIO lines 0 to 15 | An edge on pin `n` of the port `SYSCFG_EXTICRn` names for line `n` is that line's edge. An edge on another port's pin is not. A selection above port K names no port. An output pin the firmware toggles is an edge as well | 20.4, 11.3.8, 11.3.10, 12.3.2 to 12.3.5 | `a_pin_edge_reaches_the_line_its_exticr_routes_it_to`, `a_pin_of_another_port_is_not_the_lines_edge`, `a_reserved_selection_routes_no_port`, `an_output_pin_the_firmware_toggles_is_an_edge_too`, `an_edge_that_arrives_before_the_routing_is_ignored`, `every_line_routes_through_its_own_exticr_field`, `a_request_that_is_not_a_pin_is_not_an_edge` |
//! | `EXTI_CPUIMR1` to `EXTI_CPUIMR3` | Unmask the interrupt of a line. A pending bit is set only for an unmasked line. Masking a pending line lowers its interrupt and leaves the bit | 20.5, Table 151, 20.6.19, 20.6.22, 20.6.25 | `an_edge_on_a_masked_line_pends_nothing`, `masking_a_pending_line_lowers_its_interrupt_and_keeps_the_bit`, `cpuimr3_and_cpuemr3_take_writes_though_the_file_types_them_read_only` |
//! | `EXTI_CPUPR1` to `EXTI_CPUPR3` | A pending bit of a configurable line, set by an enabled edge on an unmasked line. Written one it clears and lowers the interrupt, written zero it does nothing. Changing a line's rising or falling selection clears it. The file gives `EXTI_CPUPR2` and `EXTI_CPUPR3` read-only, so the model applies their clearing itself | 20.5, 20.6.21, 20.6.24, 20.6.27 | `writing_one_clears_a_pending_bit_and_lowers_the_interrupt`, `writing_zero_leaves_a_pending_bit`, `changing_the_selected_edge_clears_the_pending_bit`, `pending_bits_of_the_second_and_third_register_clear_by_a_one`, `a_second_edge_on_a_pending_line_changes_no_level`, `the_pending_registers_take_the_rule_the_part_gives_them` |
//! | The interrupts | The first sixteen lines each ask the vector Table 147 gives them: `EXTI0` to `EXTI4` on 6 to 10, lines 5 to 9 together on `EXTI9_5`, 23, and lines 10 to 15 together on `EXTI15_10`, 40. Line 16, the voltage detector, asks 1. A shared vector stays up while any of its lines stands | 20.4, Table 150, Table 147 | `each_line_raises_the_vector_table_147_gives_it`, `a_shared_vector_stays_up_until_every_line_is_clear`, `the_positions_in_the_instances_table_are_table_147s_lines` |
//! | `EXTI_SWIER1` to `EXTI_SWIER3` | Writing one triggers the line like an edge: it needs a trigger edge selected and the interrupt unmasked. The register reads zero, the bit clearing itself. Only a configurable line has one | 20.5, 20.5.5, 20.6.3, 20.6.9, 20.6.15 | `swier_pends_an_enabled_line_and_clears_itself`, `swier_needs_an_edge_selected_and_the_interrupt_unmasked`, `swier_reaches_the_voltage_detector_line_and_the_configurable_lines_beyond_the_gpios` |
//! | `EXTI_CPUEMR1` to `EXTI_CPUEMR3` | Stored. The event goes to the core's event input, which has no model, and sets no pending bit | 20.5, 20.6.20, 20.6.23, 20.6.26 | `the_event_masks_and_the_d3_registers_are_stored` |
//! | `EXTI_D3PMR1` to `EXTI_D3PMR3` | Stored: the D3 pending mask of each line that can wake the D3 domain | 20.3.2, 20.5.4, 20.6.4, 20.6.10, 20.6.16 | `the_event_masks_and_the_d3_registers_are_stored` |
//! | `EXTI_D3PCR1L`, `EXTI_D3PCR1H`, `EXTI_D3PCR2L`, `EXTI_D3PCR2H`, `EXTI_D3PCR3H` | Stored: which of four sources clears each line's D3 pending request | 20.3.5, 20.6.5, 20.6.6, 20.6.11, 20.6.12, 20.6.18 | `the_event_masks_and_the_d3_registers_are_stored` |
//! | The direct lines | Stored: their interrupt and event masks, and the trigger registers where the file has a bit. A direct event is rising edge only and has no pending bit here, its interrupt being its peripheral's own | 20.1, 20.3.3, 20.5 | `the_direct_lines_keep_their_masks_and_set_no_pending_bit` |
//! | Reset | The registers, the trigger shadow and the interrupts go back. The ports reset with the part and announce low again, so the levels go back to low too | 20.6, 11.3.13 | `a_reset_clears_the_pending_lines_and_the_levels` |
//! | A routing read the bus refuses | The session is told once. The edge is lost, since the routing can't be known | 12.3.2, 8.7.48 | `a_refused_exticr_read_is_told_once_and_the_edge_is_lost` |
//! | The driver's sequences | ChibiOS's `_pal_lld_enablepadevent` and its `EXTI5_9` handler, register access by register access, on the bus | 20.5.1 | `chibios_enables_a_pad_event_and_serves_it` |
//! | The model's state | It serializes, so the part's snapshot keeps it | n/a | `the_model_state_round_trips_through_a_snapshot` |
//!
//! **What the model leaves out, and why.**
//!
//! | Behavior | Source | Reason |
//! |---|---|---|
//! | The core's event output and the wakeups | 20.2, 20.5, 20.5.3 | The event goes to `rxev`, and the wakeups go to the power controller. The emulator has no event input, and the part has no low-power modes. The event mask bits are stored and a masked or unmasked event changes nothing here |
//! | The D3 domain: its pending request logic, its pendclear sources and its interrupt | 20.3.2, 20.3.4, 20.3.5, 20.5.4 | Power domains aren't modeled. The registers that configure it are stored, see above |
//! | An edge that arrives while a trigger register is written | 20.6.1 note | "If a rising edge on the Configurable event input occurs during writing of the register, the associated pending bit will not be set." A write and an edge at one instant have an order the model gives them and the manual gives no finer. The model orders them as they arrive |
//! | The synchronization delay of the edge detector | 20.3.1, Figure 91 | The manual gives no number of clock cycles, so the pending bit and the interrupt follow the edge at its own instant |
//! | A change of the routing moving a line | 12.3.2 | `SYSCFG_EXTICRn` isn't watched. A selection change that moves a line from a pin at one level to a pin at the other is an edge in hardware, and the model finds it only when a pin changes. The part has nothing that tells it of the write |
//! | An input that has never been driven or pulled | 11.4.5 | The model starts every pin low, as the port does, so the first high a board puts on a floating pin is a rising edge |
//! | The lines no model drives: 17 and 18 and 19 from the RTC, 20 and 21 from the comparators, 49 and 51 from the low-power timers, 85 from the HDMI-CEC, 86 from the Ethernet wakeup, and the 82 and 84 the register pages define | 20.4, Table 150 | No instance drives an edge into the controller yet, so only `EXTI_SWIER` pends them. The vector of each is its owner's own line in Table 147, and a model raises only its own instance's lines, so a pending bit here raises none |
//! | The lines of Table 150 that are direct events, 22 to 44, 47, 48, 50, 52 to 76 and 87 | 20.1, 20.4 | Their interrupts are the peripherals' own. The controller holds the masks and the file's trigger bits, and no pending bit exists for them |
//! | A narrower access than the word every register needs | 20.6 | "A byte or half-word cannot be read or written." The register file lands a narrower store in its lane regardless |
//! | Reserved bits of the mask registers | 20.6.22, 20.6.25 | Bits the manual says to keep at reset value read back what was written, like every other reserved bit of every register this part models |
//! | `EXTI_D3PCR3L` | 20.6.17 | ST's file has no register at `0x50`, and its whole page is reserved |
//! | ES0392 rev 15: the RTC interrupts that share an EXTI line can mask each other, and the RTC alarm flag set again under debug | ES0392 rev 15 | RTC items, and the RTC isn't wired to this controller. No ES0392 item names the controller's own behavior, which the whole errata chapter was read for |
//!
//! **Where this model departs from the vendor's file.** The file gives `EXTI_CPUIMR2` and `EXTI_CPUIMR3`
//! zero where sections 20.6.22 and 20.6.25 give `0xFFF5_FFFF` and `0x018B_FFFF`. It gives the three
//! pending registers plain read-write where section 20.6.21 and the two after it type each bit `rc1`,
//! so the part gives them `WriteModify::OneToClear`. It types `EXTI_CPUPR2`, `EXTI_CPUIMR3`,
//! `EXTI_CPUEMR3` and `EXTI_CPUPR3` read-only where the pages type the first and the last `rc1` and the
//! two between read-write, which the model's own writes make right. It names the registers `CPUxx` as
//! revision 9 of the manual does.
//!
//! **Determinism.** The model reads no clock and draws no number. The same edges and accesses in the
//! same order give the same pending bits and interrupts, and `the_model_state_round_trips_through_a_snapshot`
//! shows the state serializes.
use crate::device::{Device, DeviceContext, Level, RegisterFile, RequestChange};
use crate::soc::stm32h743::generated::exti::Register;
use crate::soc::stm32h743::generated::syscfg::Register as SyscfgRegister;
use crate::soc::{FieldSpec, Layout, Width};
use super::gpio::{PORTS, Pin};
/// The three pending registers, 20.6.21, 20.6.24 and 20.6.27, which the part gives write-one-to-clear.
/// The file lands a plain write, which would erase the bits the firmware didn't write, so the rule
/// can't be left to the model's own arm.
pub const PENDING: [Register; 3] = [Register::Cpupr1, Register::Cpupr2, Register::Cpupr3];
// Positions in the instance's interrupt table, whose lines are Table 147's: PVD_PVM is line 1 from
// EXTI line 16, EXTI0 to EXTI4 are 6 to 10, EXTI9_5 is 23 and EXTI15_10 is 40.
const PVD_PVM: usize = 0;
const EXTI0: usize = 1;
const EXTI9_5: usize = 6;
const EXTI15_10: usize = 7;
const VECTORS: usize = 8;
const ROUTES: [(usize, u32, u32); VECTORS] = [
(PVD_PVM, 16, 16),
(EXTI0, 0, 0),
(EXTI0 + 1, 1, 1),
(EXTI0 + 2, 2, 2),
(EXTI0 + 3, 3, 3),
(EXTI0 + 4, 4, 4),
(EXTI9_5, 5, 9),
(EXTI15_10, 10, 15),
];
const LINES: usize = 16;
const PINS_PER_PORT: u64 = 16;
const PORT_COUNT: usize = PORTS.len();
const RISING: usize = 0;
const FALLING: usize = 1;
struct Bank {
rising: Register,
falling: Register,
software: Register,
interrupt: Register,
pending: Register,
}
const BANKS: [Bank; 3] = [
Bank {
rising: Register::Rtsr1,
falling: Register::Ftsr1,
software: Register::Swier1,
interrupt: Register::Cpuimr1,
pending: Register::Cpupr1,
},
Bank {
rising: Register::Rtsr2,
falling: Register::Ftsr2,
software: Register::Swier2,
interrupt: Register::Cpuimr2,
pending: Register::Cpupr2,
},
Bank {
rising: Register::Rtsr3,
falling: Register::Ftsr3,
software: Register::Swier3,
interrupt: Register::Cpuimr3,
pending: Register::Cpupr3,
},
];
const ROUTING: [SyscfgRegister; 4] = [
SyscfgRegister::Exticr1,
SyscfgRegister::Exticr2,
SyscfgRegister::Exticr3,
SyscfgRegister::Exticr4,
];
const ROUTING_REFUSED: &str = "a pin edge could not be routed: the bus refused the read of SYSCFG_EXTICRn, which a part gates behind RCC_APB4ENR.SYSCFGEN, RM0433 rev 9 sections 12.3.2 and 8.7.48";
/// The extended interrupt and event controller.
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct Exti {
syscfg: u64,
// The last level each port announced for each pin, low before the first, which is where an
// undriven input sits. A request names the new level alone, so the edge is found against this.
levels: [[Level; LINES]; PORT_COUNT],
// The rising and falling registers of each bank as the model last saw them. The file already
// holds the value just written, and a change of either is what clears a pending bit.
triggers: [[u64; 2]; BANKS.len()],
// The level each position of the instance's table was last driven to, since a model reports changes.
raised: [Level; VECTORS],
// Whether the session was told of the last refused routing read, so that it's told once.
refused: bool,
}
impl Exti {
/// The controller of a part whose SYSCFG instance sits at `syscfg_base`, which holds the
/// `SYSCFG_EXTICRn` registers it reads to route a pin. The part also names this instance with
/// `reads_requests`, and holds its eleven ports under [`PORTS`].
#[must_use]
pub const fn new(syscfg_base: u64) -> Self {
Self {
syscfg: syscfg_base,
levels: [[Level::Low; LINES]; PORT_COUNT],
triggers: [[0; 2]; BANKS.len()],
raised: [Level::Low; VECTORS],
refused: false,
}
}
fn routing_address(&self, line: Pin) -> u64 {
let register = ROUTING[line.index() / 4];
self.syscfg + u64::from(register.spec().offset)
}
fn enabled(registers: &RegisterFile, bank: &Bank, lines: u64) -> u64 {
(registers.get(bank.rising) | registers.get(bank.falling)) & lines
}
fn pend(registers: &mut RegisterFile, bank: &Bank, lines: u64) {
let unmasked = lines & registers.get(bank.interrupt);
registers.set(bank.pending, registers.get(bank.pending) | unmasked);
}
fn select(
&mut self,
bank: usize,
side: usize,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
let table = &BANKS[bank];
let register = if side == RISING {
table.rising
} else {
table.falling
};
let now = registers.get(register);
let changed = (self.triggers[bank][side] ^ now) & lines_of(table);
self.triggers[bank][side] = now;
registers.set(table.pending, registers.get(table.pending) & !changed);
self.drive_interrupts(registers, context);
}
fn software(
&mut self,
bank: usize,
value: u64,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
let table = &BANKS[bank];
let lines = Self::enabled(registers, table, value & lines_of(table));
Self::pend(registers, table, lines);
registers.set(table.software, 0);
self.drive_interrupts(registers, context);
}
fn drive_interrupts(&mut self, registers: &RegisterFile, context: &mut DeviceContext<'_>) {
let active = registers.get(Register::Cpupr1) & registers.get(Register::Cpuimr1);
for (position, first, last) in ROUTES {
let level = Level::from(active & lines_between(Register::Cpupr1, first, last) != 0);
if self.raised[position] != level {
self.raised[position] = level;
context.interrupt(position, level);
}
}
}
}
impl Device for Exti {
type Register = Register;
fn write(
&mut self,
register: Register,
value: u64,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
match register {
// 20.6.1, 20.6.7, 20.6.13: the rising edge selections. Changing one clears the line's pending bit.
Register::Rtsr1 => self.select(0, RISING, registers, context),
Register::Rtsr2 => self.select(1, RISING, registers, context),
Register::Rtsr3 => self.select(2, RISING, registers, context),
// 20.6.2, 20.6.8, 20.6.14: the falling edge selections.
Register::Ftsr1 => self.select(0, FALLING, registers, context),
Register::Ftsr2 => self.select(1, FALLING, registers, context),
Register::Ftsr3 => self.select(2, FALLING, registers, context),
// 20.6.3, 20.6.9, 20.6.15: a one triggers the line and reads back as zero.
Register::Swier1 => self.software(0, value, registers, context),
Register::Swier2 => self.software(1, value, registers, context),
Register::Swier3 => self.software(2, value, registers, context),
// 20.6.19, 20.6.22, 20.6.25: the interrupt masks gate the pending bits and the vectors. The
// file types the third read-only, so the model lands the write.
Register::Cpuimr1 | Register::Cpuimr2 | Register::Cpuimr3 => {
registers.set(register, value);
self.drive_interrupts(registers, context);
}
// 20.6.20, 20.6.23, 20.6.26: the event masks, which the file types read-only for the third.
Register::Cpuemr1 | Register::Cpuemr2 | Register::Cpuemr3 => {
registers.set(register, value);
}
// 20.6.21, 20.6.24, 20.6.27: a one clears the pending bit, and the file drops the write of two.
Register::Cpupr1 | Register::Cpupr2 | Register::Cpupr3 => {
registers.set(register, registers.get(register) & !value);
self.drive_interrupts(registers, context);
}
// 20.6.4, 20.6.10, 20.6.16: the D3 pending masks. 20.6.5, 20.6.6, 20.6.11, 20.6.12, 20.6.18:
// the D3 clear sources. The file stores each.
Register::D3pmr1
| Register::D3pmr2
| Register::D3pmr3
| Register::D3pcr1l
| Register::D3pcr1h
| Register::D3pcr2l
| Register::D3pcr2h
| Register::D3pcr3h => {}
}
}
// Notes an edge of a pin and asks which line the firmware routed it to, 20.4. A change no selected
// edge takes is left alone and costs no read.
fn on_request(
&mut self,
request: RequestChange,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
let Some(port) = PORTS.iter().position(|name| *name == request.instance) else {
return;
};
let Some(pin) = u8::try_from(request.request).ok().and_then(Pin::new) else {
return;
};
let before = std::mem::replace(&mut self.levels[port][pin.index()], request.level);
if before == request.level {
return;
}
let edge = if request.level == Level::High {
BANKS[0].rising
} else {
BANKS[0].falling
};
if registers.get(edge) & line_mask(edge, pin) == 0 {
return;
}
context.read_from(self.routing_address(pin), Width::W32, token(port, pin));
}
fn on_access(
&mut self,
token: u64,
value: u64,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
self.refused = false;
let (port, pin) = untoken(token);
if routed_port(pin, value) == Some(port) {
Self::pend(registers, &BANKS[0], line_mask(BANKS[0].pending, pin));
self.drive_interrupts(registers, context);
}
}
// A part gates the SYSCFG behind its clock enable, and a firmware that routes a pin before enabling
// the clock has not routed it, so the edge has no line to take, 12.3.2.
fn on_refused(
&mut self,
_token: u64,
_registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
if !self.refused {
self.refused = true;
context.tell(Register::Cpupr1.spec().name, ROUTING_REFUSED);
}
}
// The ports reset with the part and announce low again, 11.3.13, so the levels go back to low too.
fn reset(&mut self) {
self.levels = [[Level::Low; LINES]; PORT_COUNT];
self.triggers = [[0; 2]; BANKS.len()];
self.raised = [Level::Low; VECTORS];
self.refused = false;
}
}
fn token(port: usize, pin: Pin) -> u64 {
u64::try_from(port).expect("a port fits") * PINS_PER_PORT + u64::from(line_bit(pin))
}
fn untoken(token: u64) -> (usize, Pin) {
let pin = u8::try_from(token % PINS_PER_PORT)
.ok()
.and_then(Pin::new)
.expect("a token holds a pin");
(
usize::try_from(token / PINS_PER_PORT).expect("a port fits"),
pin,
)
}
// The port the firmware routed line `pin` to, from the `SYSCFG_EXTICRn` that holds it, or `None` for a
// selection above port K, which section 12.3.2 reserves.
fn routed_port(pin: Pin, exticr: u64) -> Option<usize> {
let register = ROUTING[pin.index() / 4];
let selection = field_at(register, (line_bit(pin) % 4) * 4).get(exticr);
usize::try_from(selection)
.ok()
.filter(|&port| port < PORT_COUNT)
}
fn field_at(register: impl Layout, bit: u32) -> &'static FieldSpec {
register
.field_at(bit)
.expect("the register has a field at the bit")
}
fn line_bit(line: Pin) -> u32 {
u32::try_from(line.index()).expect("a line is below sixteen")
}
fn line_mask(register: Register, line: Pin) -> u64 {
register.field_at(line_bit(line)).map_or(0, FieldSpec::mask)
}
// The rising register's fields are the configurable lines, which the pending register's page defines too.
fn lines_of(bank: &Bank) -> u64 {
bank.rising
.spec()
.fields
.iter()
.fold(0, |lines, field| lines | field.mask())
}
fn lines_between(register: Register, first: u32, last: u32) -> u64 {
register
.spec()
.fields
.iter()
.filter(|field| (first..=last).contains(&u32::from(field.bit)))
.fold(0, |lines, field| lines | field.mask())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::device::{ErasedDevice, InterruptChange, ResetCause, Told};
use crate::emulator::{Error, Memory};
use crate::mcu::{Mcu, Operate, Refused};
use crate::soc::stm32h743::builder;
use crate::soc::stm32h743::generated::gpioa;
use crate::soc::stm32h743::generated::{EXTI, SYSCFG};
use crate::soc::stm32h743::peripherals::gpio::Gpio;
use crate::soc::stm32h743::peripherals::syscfg::Syscfg;
use crate::soc::{Bus, Fault, Gate, WriteModify};
use crate::time::VirtualNanos;
// Table 147's lines for the vectors the controller drives: `PVD_PVM`, `EXTI0` to `EXTI4`, `EXTI9_5`
// and `EXTI15_10`.
const TABLE_147: [u16; VECTORS] = [1, 6, 7, 8, 9, 10, 23, 40];
const A: usize = 0;
const B: usize = 1;
const C: usize = 2;
const K: usize = 10;
/// Memory the tests never touch: the controller asks for registers alone.
struct NoMemory;
impl Memory for NoMemory {
fn read(&mut self, address: u64, width: Width) -> Result<u64, Error> {
Err(Error::Memory { address, width })
}
fn write(&mut self, address: u64, width: Width, _value: u64) -> Result<(), Error> {
Err(Error::Memory { address, width })
}
}
/// The instances this wiring joins, on one bus: the eleven ports, the SYSCFG, and the controller.
struct Wired {
bus: Bus,
ports: [Gpio; PORT_COUNT],
syscfg: Syscfg,
exti: Exti,
now: VirtualNanos,
refused: Vec<Refused>,
interrupts: Vec<InterruptChange>,
}
impl Mcu for Wired {
fn bus(&self) -> &Bus {
&self.bus
}
fn bus_mut(&mut self) -> &mut Bus {
&mut self.bus
}
fn reset(&mut self, _at: VirtualNanos, _cause: ResetCause) {
self.reset_every_model();
}
fn poll(&mut self, now: VirtualNanos) {
self.poll_every_model(now);
}
fn split(&mut self, slot: usize) -> (&mut Bus, Option<&mut dyn ErasedDevice>) {
let name = self.bus.slots()[slot].peripheral().name;
let Self {
bus,
ports,
syscfg,
exti,
..
} = self;
let model: Option<&mut dyn ErasedDevice> = match name {
"EXTI" => Some(exti),
"SYSCFG" => Some(syscfg),
port => PORTS
.iter()
.position(|candidate| *candidate == port)
.map(|nth| -> &mut dyn ErasedDevice { &mut ports[nth] }),
};
(bus, model)
}
}
impl Wired {
// The rig holds no clock controller, so it drops the description's clock gates, as `Single` does.
fn new() -> Self {
Self::on(builder().ungated().reads_requests("EXTI"))
}
fn on(builder: crate::soc::Builder) -> Self {
let mut bus = builder.build().expect("the bus builds");
bus.programmed_only_by("EXTI");
let mut wired = Self {
bus,
ports: std::array::from_fn(|_| Gpio::default()),
syscfg: Syscfg,
exti: Exti::new(SYSCFG.base),
now: VirtualNanos(0),
refused: Vec::new(),
interrupts: Vec::new(),
};
wired.check().expect("every model sits on its own layout");
wired
}
fn settle(&mut self) {
let refused = Operate::settle(self, &mut NoMemory).expect("the settle runs");
self.refused.extend(refused);
let changes = self.drain_interrupts();
self.interrupts.extend(changes);
}
fn tick(&mut self) -> VirtualNanos {
self.now = VirtualNanos(self.now.0 + 1);
self.now
}
fn read(&mut self, address: u64) -> u64 {
let at = self.tick();
Operate::read(self, at, address, Width::W32).expect("the register answers")
}
fn write(&mut self, address: u64, value: u64) {
let at = self.tick();
Operate::write(self, at, address, Width::W32, value).expect("the register takes it");
self.settle();
}
fn exti_address(register: Register) -> u64 {
EXTI.base + u64::from(register.spec().offset)
}
fn syscfg_address(register: SyscfgRegister) -> u64 {
SYSCFG.base + u64::from(register.spec().offset)
}
fn read_exti(&mut self, register: Register) -> u64 {
self.read(Self::exti_address(register))
}
fn write_exti(&mut self, register: Register, value: u64) {
self.write(Self::exti_address(register), value);
}
/// Routes line `pin` to `port`, as the firmware's `SYSCFG_EXTICR` read-modify-write does.
fn route(&mut self, pin: u8, port: usize) {
let register = ROUTING[usize::from(pin) / 4];
let address = Self::syscfg_address(register);
let current = self.read(address);
let field = field_at(register, u32::from(pin % 4) * 4);
self.write(
address,
field.set(current, u64::try_from(port).expect("a port")),
);
}
/// What the firmware does to set up a line: select the edges, then unmask it.
fn enable(&mut self, pin: u8, edges: Edges) {
let line = bit(Register::Rtsr1, pin);
let (rising, falling) = edges.selected();
for (register, on) in [(Register::Rtsr1, rising), (Register::Ftsr1, falling)] {
let current = self.read_exti(register);
self.write_exti(register, if on { current | line } else { current & !line });
}
let mask = self.read_exti(Register::Cpuimr1);
self.write_exti(Register::Cpuimr1, mask | line);
}
fn port_address(&self, port: usize, register: gpioa::Register) -> u64 {
let base = self
.bus
.peripheral(PORTS[port])
.expect("a port is an instance")
.base;
base + u64::from(register.spec().offset)
}
fn write_port(&mut self, port: usize, register: gpioa::Register, value: u64) {
let address = self.port_address(port, register);
self.write(address, value);
}
/// The outside world driving a pin, as a board does, at the next instant.
fn drive(&mut self, port: usize, pin: u8, level: Option<Level>) {
let at = self.tick();
let slot = self.bus.slot_of(PORTS[port]).expect("a port has a slot");
let Self { bus, ports, .. } = self;
let (registers, mut context) = bus.drive(at, slot);
ports[port].drive(pin_of(pin), level, registers, &mut context);
self.settle();
}
/// Makes a pin of a port an input, as the firmware's pin setup does.
fn make_input(&mut self, port: usize, pin: u8) {
self.set_mode(port, pin, 0b00);
}
fn set_mode(&mut self, port: usize, pin: u8, mode: u64) {
let address = self.port_address(port, gpioa::Register::Moder);
let moder = self.read(address);
let field = field_at(gpioa::Register::Moder, u32::from(pin) * 2);
self.write(address, field.set(moder, mode));
}
/// The vectors that went up and down since the last call, as the lines of the part's table.
fn vectors(&mut self) -> Vec<(u16, Level)> {
self.settle();
self.interrupts
.drain(..)
.map(|change| (change.interrupt, change.level))
.collect()
}
fn pending(&mut self) -> u64 {
self.read_exti(Register::Cpupr1)
}
}
fn pin_of(index: u8) -> Pin {
Pin::new(index).expect("a line of the first sixteen")
}
/// The bit of line `line` in `register`, from the generated table.
fn bit(register: Register, line: u8) -> u64 {
field_at(register, u32::from(line)).mask()
}
/// The trigger edges a test selects for a line.
#[derive(Clone, Copy)]
enum Edges {
Rising,
Falling,
Both,
}
impl Edges {
fn selected(self) -> (bool, bool) {
match self {
Self::Rising => (true, false),
Self::Falling => (false, true),
Self::Both => (true, true),
}
}
}
/// An input on `port` that a board can drive, with line `pin` routed to it and enabled for the edges.
fn armed(port: usize, pin: u8, edges: Edges) -> Wired {
let mut wired = Wired::new();
wired.make_input(port, pin);
wired.route(pin, port);
wired.enable(pin, edges);
wired.vectors();
wired
}
#[test]
fn every_register_comes_out_of_reset_as_the_manual_gives_it() {
/// Sections 20.6.1 to 20.6.27, from the page. The pending registers' `undefined` reads zero.
const MANUAL: [(Register, u64); 26] = [
(Register::Rtsr1, 0),
(Register::Ftsr1, 0),
(Register::Swier1, 0),
(Register::D3pmr1, 0),
(Register::D3pcr1l, 0),
(Register::D3pcr1h, 0),
(Register::Rtsr2, 0),
(Register::Ftsr2, 0),
(Register::Swier2, 0),
(Register::D3pmr2, 0),
(Register::D3pcr2l, 0),
(Register::D3pcr2h, 0),
(Register::Rtsr3, 0),
(Register::Ftsr3, 0),
(Register::Swier3, 0),
(Register::D3pmr3, 0),
(Register::D3pcr3h, 0),
(Register::Cpuimr1, 0xFFC0_0000),
(Register::Cpuemr1, 0),
(Register::Cpupr1, 0),
(Register::Cpuimr2, 0xFFF5_FFFF),
(Register::Cpuemr2, 0),
(Register::Cpupr2, 0),
(Register::Cpuimr3, 0x018B_FFFF),
(Register::Cpuemr3, 0),
(Register::Cpupr3, 0),
];
let mut wired = Wired::new();
let listed: Vec<Register> = MANUAL.iter().map(|(register, _)| *register).collect();
assert_eq!(
listed,
Register::ALL,
"the manual's table covers every register"
);
for (register, value) in MANUAL {
assert_eq!(wired.read_exti(register), value, "{}", register.spec().name);
}
}
#[test]
fn a_rising_edge_pends_its_line_and_raises_its_vector() {
let mut wired = armed(C, 3, Edges::Rising);
assert_eq!(wired.pending(), 0);
wired.drive(C, 3, Some(Level::High));
assert_eq!(wired.pending(), bit(Register::Cpupr1, 3), "20.6.21");
assert_eq!(
wired.vectors(),
[(9, Level::High)],
"EXTI3 is line 9, Table 147"
);
}
#[test]
fn a_falling_edge_pends_its_line() {
let mut wired = armed(B, 7, Edges::Falling);
wired.drive(B, 7, Some(Level::High));
assert_eq!(wired.pending(), 0, "a rising edge is not the selected one");
wired.drive(B, 7, Some(Level::Low));
assert_eq!(wired.pending(), bit(Register::Cpupr1, 7));
}
#[test]
fn both_edges_pend_a_line_that_selects_both() {
let mut wired = armed(A, 5, Edges::Both);
wired.drive(A, 5, Some(Level::High));
assert_eq!(wired.pending(), bit(Register::Cpupr1, 5), "rising, 20.6.1");
wired.write_exti(Register::Cpupr1, bit(Register::Cpupr1, 5));
assert_eq!(wired.pending(), 0);
wired.drive(A, 5, Some(Level::Low));
assert_eq!(wired.pending(), bit(Register::Cpupr1, 5), "falling, 20.6.2");
}
#[test]
fn a_line_with_no_edge_selected_pends_nothing() {
let mut wired = Wired::new();
wired.make_input(C, 3);
wired.route(3, C);
let mask = wired.read_exti(Register::Cpuimr1);
wired.write_exti(Register::Cpuimr1, mask | bit(Register::Cpuimr1, 3));
wired.drive(C, 3, Some(Level::High));
wired.drive(C, 3, Some(Level::Low));
assert_eq!(
wired.pending(),
0,
"an enabled line needs an edge selected, 20.5"
);
assert!(wired.vectors().is_empty());
}
#[test]
fn a_pin_edge_reaches_the_line_its_exticr_routes_it_to() {
let mut wired = armed(C, 3, Edges::Rising);
wired.drive(C, 3, Some(Level::High));
assert_eq!(wired.pending(), bit(Register::Cpupr1, 3));
let exticr1 = wired.read(Wired::syscfg_address(SyscfgRegister::Exticr1));
assert_eq!(
field_at(SyscfgRegister::Exticr1, 12).get(exticr1),
2,
"line 3 was routed to port C, which SYSCFG_EXTICR1 numbers 2, 12.3.2"
);
}
#[test]
fn a_pin_of_another_port_is_not_the_lines_edge() {
let mut wired = armed(C, 3, Edges::Both);
wired.make_input(A, 3);
wired.drive(A, 3, Some(Level::High));
wired.drive(A, 3, Some(Level::Low));
wired.make_input(B, 4);
wired.drive(B, 4, Some(Level::High));
assert_eq!(
wired.pending(),
0,
"20.4: line 3 takes pin 3 of port C alone"
);
assert!(wired.vectors().is_empty());
wired.drive(C, 3, Some(Level::High));
assert_eq!(wired.pending(), bit(Register::Cpupr1, 3));
}
#[test]
fn a_reserved_selection_routes_no_port() {
let mut wired = Wired::new();
wired.make_input(A, 3);
let exticr1 = Wired::syscfg_address(SyscfgRegister::Exticr1);
wired.write(exticr1, field_at(SyscfgRegister::Exticr1, 12).set(0, 0xB));
wired.enable(3, Edges::Both);
wired.drive(A, 3, Some(Level::High));
wired.drive(A, 3, Some(Level::Low));
assert_eq!(
wired.pending(),
0,
"selections above port K are reserved, 12.3.2"
);
}
#[test]
fn every_line_routes_through_its_own_exticr_field() {
let mut wired = Wired::new();
for pin in 0..16 {
wired.make_input(K, pin);
wired.route(pin, K);
wired.enable(pin, Edges::Rising);
}
wired.vectors();
for pin in 0..16 {
wired.drive(K, pin, Some(Level::High));
}
assert_eq!(
wired.pending(),
lines_between(Register::Cpupr1, 0, 15),
"port K feeds all sixteen lines"
);
for line in 0..16_u32 {
let register = ROUTING[usize::try_from(line / 4).unwrap()];
let field = field_at(register, (line % 4) * 4);
assert_eq!(field.name, format!("EXTI{line}"));
assert_eq!(field.get(wired.read(Wired::syscfg_address(register))), 10);
}
}
#[test]
fn an_output_pin_the_firmware_toggles_is_an_edge_too() {
let mut wired = armed(C, 13, Edges::Both);
wired.set_mode(C, 13, 0b01);
assert_eq!(
wired.pending(),
0,
"an output coming up low is the level the model started at"
);
wired.write_port(C, gpioa::Register::Bsrr, gpioa::bsrr::BS13.mask());
assert_eq!(
wired.pending(),
bit(Register::Cpupr1, 13),
"the pad is the input in output mode, 11.3.10"
);
}
#[test]
fn an_edge_that_arrives_before_the_routing_is_ignored() {
let mut wired = Wired::new();
wired.make_input(C, 3);
wired.enable(3, Edges::Rising);
wired.drive(C, 3, Some(Level::High));
assert_eq!(
wired.pending(),
0,
"line 3 still takes port A, which reset selects"
);
wired.drive(C, 3, Some(Level::Low));
wired.route(3, C);
wired.drive(C, 3, Some(Level::High));
assert_eq!(
wired.pending(),
bit(Register::Cpupr1, 3),
"routed, it is an edge"
);
}
#[test]
fn a_request_that_is_not_a_pin_is_not_an_edge() {
let mut wired = armed(C, 3, Edges::Both);
let slot = wired.bus.slot_of("EXTI").expect("an instance");
let Wired { bus, exti, .. } = &mut wired;
for (instance, request) in [("SPI1", 3), ("GPIOC", 16), ("GPIOC", usize::MAX)] {
let (registers, mut context) = bus.drive(VirtualNanos(50), slot);
let change = RequestChange {
instance,
request,
level: Level::High,
};
Device::on_request(exti, change, registers, &mut context);
}
wired.settle();
assert_eq!(wired.pending(), 0, "only pins 0 to 15 of a port are lines");
}
#[test]
fn an_edge_on_a_masked_line_pends_nothing() {
let mut wired = armed(C, 3, Edges::Rising);
let mask = wired.read_exti(Register::Cpuimr1);
wired.write_exti(Register::Cpuimr1, mask & !bit(Register::Cpuimr1, 3));
wired.drive(C, 3, Some(Level::High));
assert_eq!(
wired.pending(),
0,
"Table 151: a masked line has no status to latch"
);
assert!(wired.vectors().is_empty());
wired.write_exti(Register::Cpuimr1, mask);
assert_eq!(wired.pending(), 0, "unmasking later finds nothing pending");
}
#[test]
fn masking_a_pending_line_lowers_its_interrupt_and_keeps_the_bit() {
let mut wired = armed(C, 3, Edges::Rising);
wired.drive(C, 3, Some(Level::High));
assert_eq!(wired.vectors(), [(9, Level::High)]);
let mask = wired.read_exti(Register::Cpuimr1);
wired.write_exti(Register::Cpuimr1, mask & !bit(Register::Cpuimr1, 3));
assert_eq!(wired.vectors(), [(9, Level::Low)], "the AND of Figure 91");
assert_eq!(
wired.pending(),
bit(Register::Cpupr1, 3),
"the bit stays latched"
);
wired.write_exti(Register::Cpuimr1, mask);
assert_eq!(wired.vectors(), [(9, Level::High)]);
}
#[test]
fn writing_one_clears_a_pending_bit_and_lowers_the_interrupt() {
let mut wired = armed(C, 3, Edges::Rising);
wired.drive(C, 3, Some(Level::High));
wired.vectors();
wired.write_exti(Register::Cpupr1, bit(Register::Cpupr1, 3));
assert_eq!(wired.pending(), 0, "20.6.21");
assert_eq!(wired.vectors(), [(9, Level::Low)]);
}
#[test]
fn writing_zero_leaves_a_pending_bit() {
let mut wired = armed(C, 3, Edges::Rising);
wired.drive(C, 3, Some(Level::High));
wired.write_exti(Register::Cpupr1, !bit(Register::Cpupr1, 3));
assert_eq!(
wired.pending(),
bit(Register::Cpupr1, 3),
"a zero does nothing"
);
}
#[test]
fn a_second_edge_on_a_pending_line_changes_no_level() {
let mut wired = armed(C, 3, Edges::Both);
wired.drive(C, 3, Some(Level::High));
wired.drive(C, 3, Some(Level::Low));
assert_eq!(wired.vectors(), [(9, Level::High)], "one rise, held");
assert_eq!(wired.pending(), bit(Register::Cpupr1, 3));
}
#[test]
fn changing_the_selected_edge_clears_the_pending_bit() {
let mut wired = armed(C, 3, Edges::Rising);
wired.drive(C, 3, Some(Level::High));
assert_eq!(wired.pending(), bit(Register::Cpupr1, 3));
let falling = wired.read_exti(Register::Ftsr1);
wired.write_exti(Register::Ftsr1, falling | bit(Register::Ftsr1, 3));
assert_eq!(
wired.pending(),
0,
"20.6.21: changing the sensitivity clears it"
);
assert_eq!(wired.vectors(), [(9, Level::High), (9, Level::Low)]);
wired.drive(C, 3, Some(Level::Low));
assert_eq!(
wired.pending(),
bit(Register::Cpupr1, 3),
"the new edge is selected"
);
let same = wired.read_exti(Register::Rtsr1);
wired.write_exti(Register::Rtsr1, same);
assert_eq!(
wired.pending(),
bit(Register::Cpupr1, 3),
"writing the selection it already has changes nothing"
);
}
#[test]
fn pending_bits_of_the_second_and_third_register_clear_by_a_one() {
let mut wired = Wired::new();
for (rising, interrupt, software, pending, line) in [
(
Register::Rtsr2,
Register::Cpuimr2,
Register::Swier2,
Register::Cpupr2,
17,
),
(
Register::Rtsr3,
Register::Cpuimr3,
Register::Swier3,
Register::Cpupr3,
20,
),
] {
let lines = bit(rising, line);
wired.write_exti(rising, lines);
let mask = wired.read_exti(interrupt);
wired.write_exti(interrupt, mask | lines);
wired.write_exti(software, lines);
assert_eq!(wired.read_exti(pending), lines, "{}", pending.spec().name);
wired.write_exti(pending, lines);
assert_eq!(
wired.read_exti(pending),
0,
"20.6.24 and 20.6.27 clear by a one"
);
}
}
#[test]
fn each_line_raises_the_vector_table_147_gives_it() {
let mut wired = Wired::new();
let lines = lines_between(Register::Rtsr1, 0, 16);
wired.write_exti(Register::Rtsr1, lines);
wired.write_exti(Register::Cpuimr1, 0xFFFF_FFFF);
wired.vectors();
for (line, vector) in [
(0_u8, 6_u16),
(1, 7),
(2, 8),
(3, 9),
(4, 10),
(5, 23),
(9, 23),
(10, 40),
(15, 40),
(16, 1),
] {
wired.write_exti(Register::Swier1, bit(Register::Swier1, line));
assert_eq!(wired.vectors(), [(vector, Level::High)], "line {line}");
wired.write_exti(Register::Cpupr1, lines);
assert_eq!(
wired.vectors(),
[(vector, Level::Low)],
"line {line} clears"
);
}
}
#[test]
fn a_shared_vector_stays_up_until_every_line_is_clear() {
let mut wired = Wired::new();
wired.write_exti(Register::Rtsr1, lines_between(Register::Rtsr1, 0, 15));
wired.write_exti(Register::Cpuimr1, 0xFFFF_FFFF);
wired.vectors();
let (five, six) = (bit(Register::Swier1, 5), bit(Register::Swier1, 6));
wired.write_exti(Register::Swier1, five | six);
assert_eq!(wired.vectors(), [(23, Level::High)], "one vector for both");
wired.write_exti(Register::Cpupr1, five);
assert!(wired.vectors().is_empty(), "line 6 still stands");
wired.write_exti(Register::Cpupr1, six);
assert_eq!(wired.vectors(), [(23, Level::Low)]);
}
#[test]
fn the_positions_in_the_instances_table_are_table_147s_lines() {
let soc = builder().build().unwrap();
let lines = soc.peripheral("EXTI").expect("an instance").interrupts;
assert_eq!(lines, TABLE_147, "the part drops the file's line 81");
assert_eq!(
[
PVD_PVM,
EXTI0,
EXTI0 + 1,
EXTI0 + 2,
EXTI0 + 3,
EXTI0 + 4,
EXTI9_5,
EXTI15_10
]
.map(|position| lines[position]),
[1, 6, 7, 8, 9, 10, 23, 40],
"PVD_PVM, EXTI0 to EXTI4, EXTI9_5 and EXTI15_10, Table 147"
);
assert_eq!(
ROUTES.map(|(position, _, _)| position),
[0, 1, 2, 3, 4, 5, 6, 7]
);
}
#[test]
fn swier_pends_an_enabled_line_and_clears_itself() {
let mut wired = Wired::new();
let line = bit(Register::Swier1, 4);
wired.write_exti(Register::Rtsr1, line);
wired.write_exti(Register::Cpuimr1, 0xFFFF_FFFF);
wired.vectors();
wired.write_exti(Register::Swier1, line);
assert_eq!(wired.pending(), line, "20.5.5");
assert_eq!(
wired.read_exti(Register::Swier1),
0,
"it always reads zero, 20.6.3"
);
assert_eq!(wired.vectors(), [(10, Level::High)]);
}
#[test]
fn swier_needs_an_edge_selected_and_the_interrupt_unmasked() {
let mut wired = Wired::new();
let line = bit(Register::Swier1, 4);
let mask = wired.read_exti(Register::Cpuimr1);
wired.write_exti(Register::Cpuimr1, mask | line);
wired.write_exti(Register::Swier1, line);
assert_eq!(
wired.pending(),
0,
"no edge selected: the line isn't enabled, 20.5"
);
wired.write_exti(Register::Ftsr1, line);
wired.write_exti(Register::Cpuimr1, mask & !line);
wired.write_exti(Register::Swier1, line);
assert_eq!(wired.pending(), 0, "masked, Table 151");
wired.write_exti(Register::Cpuimr1, mask | line);
wired.write_exti(Register::Swier1, line);
assert_eq!(wired.pending(), line, "either edge enables it");
}
#[test]
fn swier_reaches_the_voltage_detector_line_and_the_configurable_lines_beyond_the_gpios() {
let mut wired = Wired::new();
let both = bit(Register::Swier1, 16) | bit(Register::Swier1, 17);
wired.write_exti(Register::Rtsr1, both);
wired.write_exti(Register::Cpuimr1, 0xFFFF_FFFF);
wired.vectors();
wired.write_exti(Register::Swier1, both);
assert_eq!(
wired.pending(),
both,
"lines 16 and 17 are configurable, 20.6.3"
);
assert_eq!(
wired.vectors(),
[(1, Level::High)],
"line 16 asks PVD_PVM, and line 17 asks the RTC's own vector, which isn't this instance's"
);
wired.write_exti(Register::Swier1, 0xFFC0_0000);
assert_eq!(
wired.pending() & 0xFFC0_0000,
0,
"a direct line has no software trigger and no pending bit"
);
}
#[test]
fn cpuimr3_and_cpuemr3_take_writes_though_the_file_types_them_read_only() {
let mut wired = Wired::new();
wired.write_exti(Register::Cpuimr3, 0x0123_4567);
assert_eq!(
wired.read_exti(Register::Cpuimr3),
0x0123_4567,
"20.6.25 types it rw"
);
wired.write_exti(Register::Cpuemr3, 0x0100_0001);
assert_eq!(
wired.read_exti(Register::Cpuemr3),
0x0100_0001,
"20.6.26 types it rw"
);
}
#[test]
fn the_event_masks_and_the_d3_registers_are_stored() {
let mut wired = Wired::new();
for register in [
Register::Cpuemr1,
Register::Cpuemr2,
Register::D3pmr1,
Register::D3pmr2,
Register::D3pmr3,
Register::D3pcr1l,
Register::D3pcr1h,
Register::D3pcr2l,
Register::D3pcr2h,
Register::D3pcr3h,
] {
wired.write_exti(register, 0x5A5A_5A5A);
assert_eq!(
wired.read_exti(register),
0x5A5A_5A5A,
"{}, 20.6",
register.spec().name
);
}
assert!(
wired.vectors().is_empty(),
"an event or D3 setting raises no vector"
);
assert_eq!(wired.pending(), 0, "an event sets no pending bit, 20.5");
}
#[test]
fn the_direct_lines_keep_their_masks_and_set_no_pending_bit() {
let mut wired = Wired::new();
assert_eq!(
wired.read_exti(Register::Cpuimr1) & 0xFFC0_0000,
0xFFC0_0000,
"direct lines are unmasked by default, 20.6.19"
);
wired.write_exti(Register::Cpuimr1, 0);
assert_eq!(
wired.read_exti(Register::Cpuimr1),
0,
"and their masks store"
);
wired.write_exti(Register::Rtsr1, 0xFFFF_FFFF);
wired.write_exti(Register::Cpuimr1, 0xFFFF_FFFF);
wired.write_exti(Register::Swier1, 0xFFFF_FFFF);
assert_eq!(
wired.pending() & 0xFFC0_0000,
0,
"no direct line has a pending bit, 20.1"
);
}
#[test]
fn a_reset_clears_the_pending_lines_and_the_levels() {
let mut wired = armed(C, 3, Edges::Rising);
wired.drive(C, 3, Some(Level::High));
assert_eq!(wired.vectors(), [(9, Level::High)]);
Mcu::reset(&mut wired, VirtualNanos(100), ResetCause::Pin);
assert_eq!(wired.pending(), 0);
assert_eq!(wired.read_exti(Register::Cpuimr1), 0xFFC0_0000);
wired.vectors();
wired.route(3, C);
wired.enable(3, Edges::Rising);
assert_eq!(
wired.pending(),
0,
"the pin is analog again and reads low, 11.3.13"
);
wired.make_input(C, 3);
assert_eq!(
wired.pending(),
bit(Register::Cpupr1, 3),
"the board still drives it high, so leaving analog mode is a rise from the low the line held"
);
}
#[test]
fn a_refused_exticr_read_is_told_once_and_the_edge_is_lost() {
let builder = builder()
.ungated()
.reads_requests("EXTI")
.gate("SYSCFG", Gate::by("RCC", "APB4ENR", "SYSCFGEN"));
let mut wired = Wired::on(builder);
wired.make_input(C, 3);
wired.enable(3, Edges::Rising);
wired.drive(C, 3, Some(Level::High));
wired.drive(C, 3, Some(Level::Low));
wired.drive(C, 3, Some(Level::High));
assert_eq!(wired.pending(), 0, "the routing couldn't be read");
let told: Vec<Told> = wired.drain_told();
assert_eq!(told.len(), 1, "told once, not on every edge: {told:?}");
assert_eq!(told[0].instance, "EXTI");
assert!(told[0].reason.contains("8.7.48"), "{}", told[0].reason);
assert!(
wired
.refused
.iter()
.all(|refused| matches!(refused.fault, Fault::Gated { .. })),
"the bus refused each as a gated access"
);
}
/// ChibiOS's `_pal_lld_enablepadevent` for PC7 on both edges, and the `EXTI5_9` handler's read and
/// clear of `EXTI->PR1`, access by access. The firmware's `(current & mask) | (port << offset)` is the
/// field's `set`.
#[test]
fn chibios_enables_a_pad_event_and_serves_it() {
let mut wired = Wired::new();
wired.make_input(C, 7);
let padmask = bit(Register::Rtsr1, 7);
let exticr2 = Wired::syscfg_address(SyscfgRegister::Exticr2);
let current = wired.read(exticr2);
wired.write(
exticr2,
field_at(SyscfgRegister::Exticr2, 12).set(current, 2),
);
for register in [Register::Rtsr1, Register::Ftsr1] {
let address = Wired::exti_address(register);
let value = wired.read(address);
wired.write(address, value | padmask);
}
let (imr1, emr1) = (
Wired::exti_address(Register::Cpuimr1),
Wired::exti_address(Register::Cpuemr1),
);
let value = wired.read(imr1);
wired.write(imr1, value | padmask);
let value = wired.read(emr1);
wired.write(emr1, value & !padmask);
wired.vectors();
wired.drive(C, 7, Some(Level::High));
assert_eq!(
wired.vectors(),
[(23, Level::High)],
"EXTI9_5 takes the edge"
);
let pr1 = Wired::exti_address(Register::Cpupr1);
let group = lines_between(Register::Cpupr1, 5, 9);
let served = wired.read(pr1) & group;
wired.write(pr1, served);
assert_eq!(
served, padmask,
"the handler finds PC7's line and nothing else"
);
assert_eq!(
wired.vectors(),
[(23, Level::Low)],
"its write lowers the vector"
);
wired.drive(C, 7, Some(Level::Low));
assert_eq!(
wired.read(pr1) & group,
padmask,
"both edges were enabled, so the falling one is served the same way"
);
}
#[test]
fn the_model_state_round_trips_through_a_snapshot() {
let mut wired = armed(C, 3, Edges::Rising);
wired.drive(C, 3, Some(Level::High));
let bytes = postcard::to_stdvec(&wired.exti).expect("the state serializes");
let restored: Exti = postcard::from_bytes(&bytes).expect("the state deserializes");
assert_eq!(postcard::to_stdvec(&restored).unwrap(), bytes);
assert_eq!(restored.levels[C][3], Level::High, "the pin levels survive");
assert_eq!(
restored.raised[4],
Level::High,
"so does what it last raised"
);
}
#[test]
fn the_pending_registers_take_the_rule_the_part_gives_them() {
let soc = builder().build().unwrap();
for register in PENDING {
assert_eq!(
soc.registers("EXTI")
.expect("an instance")
.write_rule(register.index()),
WriteModify::OneToClear,
"{}",
register.spec().name
);
}
}
}