Repository: quartiq/stabilizer (language: rust)

Issue:
support reboot into dfu
* see `booster` for how this is done on F4
* research  robust sequence for H7

https://github.com/quartiq/stabilizer/blob/19c6fa6381f288058a1c49d7bddc3a1adf0a1ee8/src/hardware/flash.rs#L126


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a31b4639d..1549acf6c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](http://keepachangelog.com/)
 and this project adheres to [Semantic Versioning](http://semver.org/).
 
+## [Unreleased](https://github.com/quartiq/stabilizer/compare/v0.9.0...main)
+
+### Added
+* Serial terminal is available on USB for settings configurations
+* Reboot to DFU support added via the serial terminal for remote bootloading
+
+### Changed
+* Broker is no longer configured at compile time, but is maintained in device memory
+
 ## [0.9.0](https://github.com/quartiq/stabilizer/compare/v0.8.1...v0.9.0)
 
 ### Fixed
diff --git a/book/src/setup.md b/book/src/setup.md
index 1b280ee23..19bc8a9d8 100644
--- a/book/src/setup.md
+++ b/book/src/setup.md
@@ -102,11 +102,10 @@ docker run -p 1883:1883 --name mosquitto -v ${pwd}/mosquitto.conf:/mosquitto/con
 
 Firmware can be loaded onto stabilizer using **one** of the three following methods.
 
-> **Note:** All methods require access to the circuit board. Pulling the device from a
-> crate always requires power removal as there are sensitive leads and components on
-> both sides of the board that may come into contact with adjacent front panels.
-> Every access to the board also requires proper ESD precautions. Never
-> hot-plug the device or the probe.
+> **Note:** Most methods below require access to the circuit board. Pulling the device from a crate
+> always requires power removal as there are sensitive leads and components on both sides of the
+> board that may come into contact with adjacent front panels. Every access to the board also
+> requires proper ESD precautions. Never hot-plug the device or the probe.
 
 ### ST-Link virtual mass storage
 
@@ -124,15 +123,24 @@ and applying power again.
 
 ### DFU Upload
 
-If an SWD/JTAG probe is not available,
-you can flash firmware using only a micro USB cable
-plugged in to the front of Stabilizer, a DFU utility, and a jumper to activate
-the bootloader.
+If an SWD/JTAG probe is not available, you can flash firmware using only a micro USB cable plugged
+in to the front of Stabilizer, and a DFU utility.
+
+> **Note:** If there is already newer firmware running on Stabilizer that supports the USB serial
+> interface, there is no need to remove Stabilizer from the crate or disconnect any existing
+> connectors/power supplies or to jumper the BOOT0 pin. Instead, open the serial port on Stabilizer
+> and request it to enter DFU mode:
+> ```bash
+> python -m serial <serial-port>
+> > platform dfu
+> ```
+>
+> After the device is in DFU mode, use the `dfu-util` command specified in the instructions below,
+> and the DFU firmware update will be complete.
 
 1. Install the DFU USB tool [`dfu-util`](http://dfu-util.sourceforge.net)
 1. Remove power
-1. Then carefully remove the module from the crate to gain
-    access to the board
+1. Then carefully remove the module from the crate to gain acccess to the board
 1. Short JC2/BOOT with the jumper
 1. Connect your computer to the Micro USB connector below/left of the RJ45
     connector on the front panel
diff --git a/memory.x b/memory.x
index 02afe9eba..ff76fc918 100644
--- a/memory.x
+++ b/memory.x
@@ -1,7 +1,7 @@
 MEMORY
 {
   ITCM   (rwx) : ORIGIN = 0x00000000, LENGTH = 64K
-  RAM    (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
+  RAM    (rwx) : ORIGIN = 0x20000000, LENGTH = 127K
   AXISRAM (rwx) : ORIGIN = 0x24000000, LENGTH = 512K
   SRAM1 (rwx) : ORIGIN = 0x30000000, LENGTH = 128K
   SRAM2 (rwx) : ORIGIN = 0x30020000, LENGTH = 128K
@@ -10,8 +10,11 @@ MEMORY
   RAM_B  (rwx) : ORIGIN = 0x38800000, LENGTH = 4K
   FLASH  (rx)  : ORIGIN = 0x08000000, LENGTH = 1024K
   FLASH1 (rx)  : ORIGIN = 0x08100000, LENGTH = 1024K
+  BOOTFLAG_RAM: ORIGIN = 0x2001FC00, LENGTH = 1K
 }
 
+_bootflag = ORIGIN(BOOTFLAG_RAM);
+
 SECTIONS {
   .axisram (NOLOAD) : ALIGN(8) {
     *(.axisram .axisram.*);
diff --git a/src/hardware/flash.rs b/src/hardware/flash.rs
index 0e69b312f..8ba2f7f9a 100644
--- a/src/hardware/flash.rs
+++ b/src/hardware/flash.rs
@@ -1,3 +1,4 @@
+use crate::hardware::platform;
 use core::fmt::Write;
 use embedded_storage::nor_flash::{NorFlash, ReadNorFlash};
 use stm32h7xx_hal::flash::LockedFlashBank;
@@ -126,12 +127,15 @@ impl serial_settings::Platform for SerialSettingsPlatform {
     fn cmd(&mut self, cmd: &str) {
         match cmd {
             "reboot" => cortex_m::peripheral::SCB::sys_reset(),
-            _ => writeln!(
-                self.interface_mut(),
-                "Invalid platform command `{cmd}`"
-            ),
+            "dfu" => platform::start_dfu_reboot(),
+            _ => {
+                writeln!(
+                    self.interface_mut(),
+                    "Invalid platform command: `{cmd}` not in [`dfu`, `reboot`]"
+                )
+                .ok();
+            }
         }
-        .ok();
     }
 
     fn settings(&self) -> &Self::Settings {
diff --git a/src/hardware/mod.rs b/src/hardware/mod.rs
index bc51a9fef..a9683f249 100644
--- a/src/hardware/mod.rs
+++ b/src/hardware/mod.rs
@@ -9,16 +9,16 @@ pub mod cpu_temp_sensor;
 pub mod dac;
 pub mod delay;
 pub mod design_parameters;
+mod eeprom;
 pub mod flash;
 pub mod input_stamper;
+pub mod platform;
 pub mod pounder;
 pub mod setup;
 pub mod shared_adc;
 pub mod signal_generator;
 pub mod timers;
 
-mod eeprom;
-
 // Type alias for the analog front-end (AFE) for ADC0.
 pub type AFE0 = afe::ProgrammableGainAmplifier<
     hal::gpio::gpiof::PF2<hal::gpio::Output<hal::gpio::PushPull>>,
diff --git a/src/hardware/platform.rs b/src/hardware/platform.rs
new file mode 100644
index 000000000..2fee79df5
--- /dev/null
+++ b/src/hardware/platform.rs
@@ -0,0 +1,77 @@
+/// Flag used to indicate that a reboot to DFU is requested.
+const DFU_REBOOT_FLAG: u32 = 0xDEAD_BEEF;
+
+/// Indicate a reboot to DFU is requested.
+pub fn start_dfu_reboot() {
+    extern "C" {
+        static mut _bootflag: u8;
+    }
+
+    unsafe {
+        let start_ptr = &mut _bootflag as *mut u8;
+        core::ptr::write_unaligned(start_ptr.cast::<u32>(), DFU_REBOOT_FLAG);
+    }
+
+    cortex_m::peripheral::SCB::sys_reset();
+}
+
+/// Check if the DFU reboot flag is set, indicating a reboot to DFU is requested.
+pub fn dfu_bootflag() -> bool {
+    // Obtain panic region start and end from linker symbol _panic_dump_start and _panic_dump_end
+    extern "C" {
+        static mut _bootflag: u8;
+    }
+
+    unsafe {
+        let start_ptr = &mut _bootflag as *mut u8;
+        let set = DFU_REBOOT_FLAG
+            == core::ptr::read_unaligned(start_ptr.cast::<u32>());
+
+        // Clear the boot flag after checking it to ensure it doesn't stick between reboots.
+        core::ptr::write_unaligned(start_ptr.cast::<u32>(), 0);
+        set
+    }
+}
+
+/// Execute the DFU bootloader stored in system memory.
+///
+/// # Note
+/// This function must be called before any system configuration is performed, as the DFU
+/// bootloader expects the system in an uninitialized state.
+pub fn execute_system_bootloader() {
+    // This process is largely adapted from
+    // https://community.st.com/t5/stm32-mcus/jump-to-bootloader-from-application-on-stm32h7-devices/ta-p/49510
+    cortex_m::interrupt::disable();
+
+    // Disable the SysTick peripheral.
+    let systick = unsafe { &*cortex_m::peripheral::SYST::PTR };
+    unsafe {
+        systick.csr.write(0);
+        systick.rvr.write(0);
+        systick.cvr.write(0);
+    }
+
+    // Clear NVIC interrupt flags and enables.
+    let nvic = unsafe { &*cortex_m::peripheral::NVIC::PTR };
+    for reg in nvic.icer.iter() {
+        unsafe {
+            reg.write(u32::MAX);
+        }
+    }
+
+    for reg in nvic.icpr.iter() {
+        unsafe {
+            reg.write(u32::MAX);
+        }
+    }
+
+    unsafe { cortex_m::interrupt::enable() };
+
+    // The chip does not provide a means to modify the BOOT pins during
+    // run-time. Jump to the bootloader in system memory instead.
+    unsafe {
+        let system_memory_address: *const u32 = 0x1FF0_9800 as *const u32;
+        log::info!("Jumping to DFU");
+        cortex_m::asm::bootload(system_memory_address);
+    }
+}
diff --git a/src/hardware/setup.rs b/src/hardware/setup.rs
index 72b09a4b9..f10fa7226 100644
--- a/src/hardware/setup.rs
+++ b/src/hardware/setup.rs
@@ -14,7 +14,7 @@ use smoltcp_nal::smoltcp;
 
 use super::{
     adc, afe, cpu_temp_sensor::CpuTempSensor, dac, delay, design_parameters,
-    eeprom, input_stamper::InputStamper, pounder,
+    eeprom, input_stamper::InputStamper, platform, pounder,
     pounder::dds_output::DdsOutput, shared_adc::SharedAdc, timers,
     DigitalInput0, DigitalInput1, EemDigitalInput0, EemDigitalInput1,
     EemDigitalOutput0, EemDigitalOutput1, EthernetPhy, NetworkStack,
@@ -248,6 +248,11 @@ pub fn setup(
         log::info!("Starting");
     }
 
+    // Check for a reboot to DFU before doing any system configuration.
+    if platform::dfu_bootflag() {
+        platform::execute_system_bootloader();
+    }
+
     let pwr = device.PWR.constrain();
     let vos = pwr.freeze();
 


Test patch: