Skip to content
Open
31 changes: 15 additions & 16 deletions app/gimlet/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ extern crate stm32h7;

use stm32h7::stm32h753 as device;

use drv_stm32h7_startup::ClockConfig;
use drv_stm32h7_startup::{ClockConfig, rolling_timer::RollingTimer};

use cortex_m_rt::entry;

Expand All @@ -34,6 +34,10 @@ fn system_init() {
let cp = cortex_m::Peripherals::take().unwrap();
let p = device::Peripherals::take().unwrap();

// Start the higher resolution timer with the default APB1 clock rate of
// 64MHz.
let timer = RollingTimer::new_tim5(&p, 64);

// Check the package we've been flashed on. Gimlet boards use BGA240.
// Gimletlet boards are very similar but use QFPs. This is designed to fail
// a Gimlet firmware that was accidentally flashed onto a Gimletlet.
Expand Down Expand Up @@ -91,20 +95,19 @@ fn system_init() {
cortex_m::asm::dsb();

// Make PC6 (SEQ_REG_TO_SP_V3P3_PG) and PC7 (SEQ_REG_TO_SP_V1P2_PG) inputs,
// then wait for both of them to go high. We time out after 1M iterations
// (with 100 cycles each), which is roughly 1.5s.
// then wait for both of them to go high. We time out after roughly 1.5s.
p.GPIOC.moder.modify(|_, w| {
w.moder6().input();
w.moder7().input()
});
const SEQ_PG: u32 = 0b11 << 6;
let mut seq_pg_okay = false;
for _ in 0..1_000_000 {
for _ in 0..1_500_000 {
if p.GPIOC.idr.read().bits() & SEQ_PG == SEQ_PG {
seq_pg_okay = true;
break;
} else {
cortex_m::asm::delay(100);
timer.blocking_delay_micros(1);
}
}
if !seq_pg_okay {
Expand All @@ -115,11 +118,11 @@ fn system_init() {
// the FPGA bitstream. The minimum CRESET pulse is 200 ns, or 13 cycles,
// but there's a 1µF capacitor on that line. Let's assume we're discharging
// the capacitor at 5 mA from 3V3; in that case, it will take 0.66 ms, or
// 42K cycles. We'll be conservative and pad it to 100K cycles.
// 42K cycles. We'll be conservative and pad it to 2ms.
p.GPIOD.bsrr.write(|w| w.bs5().set());
p.GPIOD.moder.modify(|_, w| w.moder5().output());
p.GPIOD.bsrr.write(|w| w.br5().reset());
cortex_m::asm::delay(100_000);
timer.blocking_delay_micros(2_000);

p.GPIOG.moder.modify(|_, w| {
w.moder0().input();
Expand All @@ -140,15 +143,8 @@ fn system_init() {
// V(t) = 1 / 50 pF * 10 µA * t
// Time to reach Vil of 2.31 V (0.7 VDD) = 11.55 µs
//
// Maximum speed of 64MHz oscillator after ST manufacturing calibration, per
// the datasheet, is 64.3 MHz.
//
// 11.55 µs @ 64.3MHz ~= 743 cycles
//
// The cortex_m delay routine is written for single-issue simple cores and
// is simply wrong on the M7 (they know this). So, let's conservatively pad
// it by a factor of 10.
cortex_m::asm::delay(743 * 10);
// Conservatively, we will wait 100 µs.
timer.blocking_delay_micros(100);

// Okay! What does the fox^Wpins say?
let rev = p.GPIOG.idr.read().bits() & 0b111;
Expand All @@ -174,6 +170,9 @@ fn system_init() {

assert_eq!(rev, expected_rev);

// Drop the timer since we're passing the peripherals by ownership here.
drop(timer);

// Do most of the setup with the common implementation.
let p = drv_stm32h7_startup::system_init_custom(
cp,
Expand Down
13 changes: 11 additions & 2 deletions app/minibar/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ extern crate stm32h7;

use stm32h7::stm32h753 as device;

use drv_stm32h7_startup::ClockConfig;
use drv_stm32h7_startup::{ClockConfig, rolling_timer::RollingTimer};

use cortex_m_rt::entry;

Expand All @@ -28,6 +28,10 @@ fn system_init() {
let cp = cortex_m::Peripherals::take().unwrap();
let p = device::Peripherals::take().unwrap();

// Start the higher resolution timer with the default APB1 clock rate of
// 64MHz.
let timer = RollingTimer::new_tim5(&p, 64);

// Check the package we've been flashed on. Minibar boards use BGA240.
// Gimletlet boards are very similar but use QFPs. This is designed to fail
// a Minibar firmware that was accidentally flashed onto a Gimletlet.
Expand Down Expand Up @@ -81,7 +85,9 @@ fn system_init() {
.pupdr7().pull_up());

// TODO: fill in timing justification here based on Sidecar's schematic.
cortex_m::asm::delay(2000);
// The previous code here waited 2000 cycles at 64MHz, or an ideal time of
// 31.25µs. We'll conservatively wait 100µs.
timer.blocking_delay_micros(100);

// Build the full ID
let rev = p.GPIOK.idr.read().bits();
Expand All @@ -104,6 +110,9 @@ fn system_init() {

assert_eq!(rev, expected_rev);

// Drop the timer since we're passing the peripherals by ownership here.
drop(timer);

drv_stm32h7_startup::system_init_custom(
cp,
p,
Expand Down
15 changes: 13 additions & 2 deletions app/psc/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ extern crate stm32h7;

use stm32h7::stm32h753 as device;

use drv_stm32h7_startup::ClockConfig;
use drv_stm32h7_startup::{ClockConfig, rolling_timer::RollingTimer};

use cortex_m_rt::entry;

Expand All @@ -28,6 +28,10 @@ fn system_init() {
let cp = cortex_m::Peripherals::take().unwrap();
let p = device::Peripherals::take().unwrap();

// Start the higher resolution timer with the default APB1 clock rate of
// 64MHz.
let timer = RollingTimer::new_tim5(&p, 64);

// We want to measure PG0-2 to determine if we're running on the correct
// board. On rev A, these pins are left floating; on later revisions, they
// are pulled either high or low.
Expand All @@ -54,7 +58,11 @@ fn system_init() {

// Wait for pins to charge / discharge (see comment in gimlet/src/main.rs
// for the actual calculations).
cortex_m::asm::delay(155 * 2);
//
// Later note: as of 2026, gimlet's main calculated a necessary time of
// 11µs, and was conservatively waiting about 10x that. We will wait a
// similar amount of time.
timer.blocking_delay_micros(100);
let rev = p.GPIOG.idr.read().bits() & 0b111;

cfg_if::cfg_if! {
Expand All @@ -68,6 +76,9 @@ fn system_init() {
}
assert_eq!(rev, expected_rev);

// Drop the timer since we're passing the peripherals by ownership here.
drop(timer);

drv_stm32h7_startup::system_init_custom(
cp,
p,
Expand Down
13 changes: 11 additions & 2 deletions app/sidecar/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ extern crate stm32h7;

use stm32h7::stm32h753 as device;

use drv_stm32h7_startup::ClockConfig;
use drv_stm32h7_startup::{ClockConfig, rolling_timer::RollingTimer};

use cortex_m_rt::entry;

Expand All @@ -28,6 +28,10 @@ fn system_init() {
let cp = cortex_m::Peripherals::take().unwrap();
let p = device::Peripherals::take().unwrap();

// Start the higher resolution timer with the default APB1 clock rate of
// 64MHz.
let timer = RollingTimer::new_tim5(&p, 64);

// Check the package we've been flashed on. Sidecar boards use BGA240.
// Gimletlet boards are very similar but use QFPs. This is designed to fail
// a Sidecar firmware that was accidentally flashed onto a Gimletlet.
Expand Down Expand Up @@ -83,7 +87,9 @@ fn system_init() {
.pupdr13().pull_up());

// TODO: fill in timing justification here based on Sidecar's schematic.
cortex_m::asm::delay(2000);
// The previous code here waited 2000 cycles at 64MHz, or an ideal time of
// 31.25µs. We'll conservatively wait 100µs.
timer.blocking_delay_micros(100);

// Build the full ID
let rev = p.GPIOC.idr.read().bits();
Expand All @@ -107,6 +113,9 @@ fn system_init() {

assert_eq!(rev, expected_rev);

// Drop the timer since we're passing the peripherals by ownership here.
drop(timer);

drv_stm32h7_startup::system_init_custom(
cp,
p,
Expand Down
131 changes: 120 additions & 11 deletions drv/stm32h7-startup/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,19 @@ use stm32h7::stm32h753 as device;
#[cfg(any(feature = "h743", feature = "h753"))]
#[pre_init]
unsafe fn system_pre_init() {
// /!\ EXTREME DANGER WARNING /!\
//
// We are running this function *before* the startup routine has completed,
// meaning that `static`s have NOT been initialized. This is extremely
// likely to be unsound in the general case, and should probably be
// rewritten in `global_asm!` some day, as the `pre_init` macro is now
// deprecated.
Comment thread
jamesmunns marked this conversation as resolved.
//
// Until that day, you MUST NOT read or write any `static` variables, as
// that would be IMMEDIATE Undefined Behavior. Tread carefully!
//
// /!\ EXTREME DANGER WARNING /!\
//
// Configure the power supply to latch the LDO on and prevent further
// reconfiguration.
//
Expand Down Expand Up @@ -114,17 +127,18 @@ pub fn system_init_custom(
// Before doing anything else, check for a measurement handoff token
#[cfg(feature = "measurement-handoff")]
unsafe {
Comment thread
jamesmunns marked this conversation as resolved.
// After each delay, we'll wait roughly 200 ms. We double the naive
// cycle count because the STM32H7 may (under some circumstances)
// dual-issue instructions in the delay loop, which would make the loop
// run twice as fast as expected. We'd rather the loop sometimes run
// twice as *slow*, because that just slows down SP boot in cases where
// the RoT is not present; if the loop is twice as fast, the SP can time
// out before RoT comes up at all, which is a much worse failure mode.
const DELAY_CYCLES: u32 = 12860000 * 2;
// After each delay, we'll wait roughly 200 ms.
//
// You might ask yourself, "how do we have a RETRY_COUNT if the closure
// diverges"? Well! `measurement_handoff::check` stores the iteration
// counter in a linker location that persists across soft-reboots.
const DELAY_MICROS: u32 = 200 * 1_000;
const RETRY_COUNT: u32 = 20;

// APB1 is currently 64MHz. Create a rolling timer we can use for now.
let timer = rolling_timer::RollingTimer::new_tim5(&p, 64);
measurement_handoff::check(RETRY_COUNT, || {
cortex_m::asm::delay(DELAY_CYCLES);
timer.blocking_delay_micros(DELAY_MICROS);
cortex_m::peripheral::SCB::sys_reset()
});
}
Expand Down Expand Up @@ -346,9 +360,104 @@ pub fn system_init_custom(
#[cfg(any(feature = "h743", feature = "h753"))]
p.RCC.d2ccip2r.modify(|_, w| w.rngsel().pll1_q());

// Hello from target speed!

// Hand the peripherals back in case the board-specific setup code needs to
// do anything.
p
}

pub mod rolling_timer {
use super::device;

/// A 32-bit rolling hardware timer, ticking at 1MHz.
pub struct RollingTimer<'a> {
tim: &'a device::TIM5,
}

/// Stop the rolling timer automatically when dropped.
impl Drop for RollingTimer<'_> {
fn drop(&mut self) {
self.tim.cr1.modify(|_r, w| w.cen().disabled());
}
}

impl<'a> RollingTimer<'a> {
/// Enable TIM5 for use as a 32-bit rolling timer at a tick rate of
/// 1MHz.
///
/// TIM5 will be enabled at the RCC level, and the current count value
/// will be reset to zero. This function may be called multiple times,
/// modulo the safety concerns listed below.
///
/// `apb1_mhz` should be the configured frequency in MHz of the APB1
/// clock, which is used as an input to TIM5, and will be used to
/// pre-scale this input down to a tick rate of 1MHz.
pub fn new_tim5(p: &'a device::Peripherals, apb1_mhz: u16) -> Self {
// Hand-build TIM5 as a 32-bit rolling timer at 1 MHz. Start by
// enabling TIM5 on APB1L in RCC and toggling reset
p.RCC.apb1lenr.modify(|_r, w| w.tim5en().enabled());
cortex_m::asm::dsb();

p.RCC.apb1lrstr.modify(|_r, w| w.tim5rst().set_bit());
p.RCC.apb1lrstr.modify(|_r, w| w.tim5rst().clear_bit());

// Now, configure it for an upcounting rolling mode
//
// Disable counter
p.TIM5.cr1.modify(|_r, w| w.cen().disabled());
// Set auto-reload to u32::MAX
p.TIM5.arr.write(|w| w.arr().bits(u32::MAX));
// Set counter to zero
p.TIM5.cnt.modify(|_r, w| w.cnt().bits(0));
// Set prescaler to (FREQ / 1M) - 1, as the counter resets to 0
// AFTER counting this number.
p.TIM5.psc.write(|w| w.psc().bits(apb1_mhz - 1));
// Generate update (latch the PSC and ARR values)
p.TIM5.egr.write(|w| w.ug().set_bit());
// Start counting!
p.TIM5.cr1.modify(|_r, w| w.cen().enabled());

Self { tim: &p.TIM5 }
}

/// Obtain the current count value of TIM5, which is a 32-bit timer that
/// ticks at a rate of 1MHz.
///
/// The value returned by this function "rolls over", or wraps around
/// every 71 minutes or so. Callers should be careful to handle
/// potential wrapping of the returned value when calculating elapsed
/// time or using for delays.
///
/// Consider using `blocking_delay_micros()`, which correctly handles
/// this calculation, for early boot-up delays.
///
/// NOTE: The returned value here is only valid while *this* instance of
/// `RollingTimer` is valid. If the timer is dropped and recreated, the
/// count will be reset to zero.
#[inline(always)]
pub fn get_rolling_micros(&self) -> u32 {
self.tim.cnt.read().bits()
}

/// Perform a blocking delay for the given number of microseconds.
#[inline]
pub fn blocking_delay_micros(&self, micros: u32) {
let start = self.get_rolling_micros();
loop {
let now = self.get_rolling_micros();

// Since this is a rolling timer, we can perform a wrapping sub
// to obtain the elapsed amount of time, even if we have crossed
// the rollover point, e.g.:
//
// start = 0xFFFF_FFFE
// now = 0x0000_0080
//
// now.wrapping_sub(start) => 0x82
let elapsed = now.wrapping_sub(start);
if elapsed >= micros {
break;
}
}
}
}
}
Loading
Loading