rigless 0.3.0

Virtual hardware-in-the-loop runtime: run unmodified firmware on an emulated MCU with every peripheral, chip model and unit of virtual time in Rust, and a CPU core of its own that translates the firmware's instructions to host code
Documentation
// Copyright (c) 2026 Rigless Labs.
// SPDX-License-Identifier: MIT OR Apache-2.0
//! The backup domain of chapter 26, as far as the reset that clears it.
//!
//! The twenty backup registers are eighty bytes of application data in the one part of the chip
//! that a reset doesn't touch, which is what a firmware uses them for. ArduPilot reads all twenty
//! before it has written any, to learn whether the reboot it just took was asked for. A register
//! file already stores bits. What makes them worth a model is the two ways they stop storing them.
//!
//! | Register or section | Behavior | Source | Test |
//! |---|---|---|---|
//! | `RTC_BKP0R` to `RTC_BKP19R` | Twenty 32-bit registers of application data, read and written whole | 26.6.20 | `every_backup_register_holds_what_was_written` |
//! | Backup domain reset | `RCC_BDCR.BDRST` "resets the entire Backup domain", so every register of this instance returns to its own reset value. The clock controller drives it as a line rather than reaching in here, and `rcc.rs` is the other end | 6.3.20 bit 16 | `a_backup_domain_reset_clears_the_backup_registers`, `the_domain_is_held_in_reset_while_the_bit_stands` |
//! | System reset | Not affected. 26.6.20 gives the registers two reset lines, and the second, "System reset: not affected", is the whole reason a firmware trusts them. A system reset rebuilds the part and carries every register of this instance over, with the clock controller's `BDCR`. `peripherals.rs` is where that happens | 26.6.20, 6.1.3 | `a_reset_keeps_the_pins_and_the_backup_domain_and_nothing_else` |
//! | Write protection | Writes are refused until `PWR_CR.DBP` stands: "the `RCC_BDCR` register, the RTC registers (including the backup registers) ... are protected against parasitic write access". Declared in `memory_map.rs`, where the part's bindings live | 5.5.1 bit 8, 26.3.5 | `a_write_without_the_backup_domain_open_is_refused` |
//! | Read access | Not gated. 26.3.5 protects against *write* access and says nothing about reads, and the part is the witness: a firmware reads the backup registers with `RTCEN` still clear | 26.3.5 | `the_backup_registers_read_without_the_rtc_clock` |
//! | `RTC_WPR` | Not needed for these registers. 26.3.5 lists the key sequence and then excepts them by name: it unlocks "all the RTC registers except for `RTC_ISR[13:8]`, `RTC_TAFCR`, and `RTC_BKPxR`" | 26.3.5 | `the_key_sequence_is_not_what_stands_between_a_firmware_and_these` |
//!
//! **What the model leaves out, and why.**
//!
//! | Behavior | Source | Reason |
//! |---|---|---|
//! | A tamper event clearing them | 26.3.13 | 26.3.13's tamper detection is not modeled, so the event cannot fire. The clearing itself is the same mechanism this module already answers, so it is the tamper unit that is missing rather than this |
//! | The calendar, the alarms, the wake-up timer, the timestamp, the calibration and the shift control | chapter 26 | The rest of chapter 26. Nothing has asked for them, and the backup registers don't depend on them. They're in the domain, not on the clock |
//! | ES0206 rev 24, 2.2.6: an MPU nonbufferable attribute over these registers treated as bufferable when the APB prescaler is one | ES0206 rev 24 | Table 3 marks it on every revision. A write the core's bus posts and completes later is below the grain the emulator models |
//! | ES0206 rev 24, 2.9.1: spurious tamper detection when disabling the tamper channel | ES0206 rev 24 | Table 3 marks it on every revision. Unreachable without the tamper unit, and a tamper event is one of the two things that clears the backup registers, so the row below says the same |
//! | ES0206 rev 24, 2.9.2: the calendar registers are not locked properly | ES0206 rev 24 | Table 3 marks it on every revision. The calendar isn't modeled |
//! | ES0206 rev 24, 2.9.3: an RTC interrupt can be masked by another RTC interrupt | ES0206 rev 24 | Table 3 marks it on every revision. The RTC's own interrupt isn't modeled |
//! | ES0206 rev 24, 2.9.4: calendar initialization may fail on consecutive INIT mode entry | ES0206 rev 24 | Table 3 marks it on every revision. The calendar isn't modeled |
//! | ES0206 rev 24, 2.9.5: the alarm flag may be repeatedly set when the core is stopped in debug | ES0206 rev 24 | Table 3 marks it on every revision. The alarm isn't modeled, and `dbg.rs` records the same erratum from `DBG_RTC_STOP`'s side |
//! | ES0206 rev 24, 2.9.6: a tamper event before the tamper detection is enabled is missed in edge mode | ES0206 rev 24 | Table 3 marks it on every revision. Unreachable without the tamper unit |

use crate::device::{Device, DeviceContext, Level, RegisterFile, RequestChange};
use crate::soc::stm32f427::generated::rtc::Register;

/// The clock controller's own numbering of the line it drives the backup domain's reset on.
///
/// A request is named by the instance driving it and that instance's number for it, so this is the
/// clock controller's to define and this module's to recognize. `rcc.rs` drives it from `BDCR`.
pub const BACKUP_DOMAIN_RESET: usize = 0;

/// The instance that drives the backup domain's reset.
const RESET_BY: &str = "RCC";

/// The backup domain.
///
/// It holds nothing of its own: what the domain stores is the register file, and what this model
/// adds is the one line that empties it. A reset is level-driven rather than an edge, so the domain
/// is put back every time the line is seen high. That's what holding `BDRST` does on the part.
#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct Rtc;

impl Device for Rtc {
    type Register = Register;

    fn on_request(
        &mut self,
        change: RequestChange,
        registers: &mut RegisterFile,
        _context: &mut DeviceContext<'_>,
    ) {
        if change.instance == RESET_BY
            && change.request == BACKUP_DOMAIN_RESET
            && change.level == Level::High
        {
            registers.reset();
        }
    }
}