Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 98 additions & 3 deletions esp-hal/src/clock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ pub(crate) mod dividers;
/// can render your device temporarily unusable. Use with caution.
/// </section>
#[doc = ""]
#[instability::unstable]
pub mod ll {
#[instability::unstable]
pub use crate::soc::clocks::*;
Expand All @@ -77,7 +76,6 @@ use crate::efuse::ChipRevision;
use crate::peripherals::PCR;
#[cfg(soc_has_clock_node_timg_calibration_clock)]
use crate::peripherals::TIMG0;
#[instability::unstable]
pub use crate::soc::clocks::ClockConfig;
pub use crate::soc::clocks::CpuClock;
use crate::{
Expand Down Expand Up @@ -123,7 +121,6 @@ impl CpuClock {
}

/// RTC Clocks.
#[instability::unstable]
pub struct RtcClock;

#[cfg(soc_has_clock_node_timg_calibration_clock)]
Expand Down Expand Up @@ -521,6 +518,104 @@ pub fn cpu_clock() -> Rate {
Rate::from_hz(ll::cpu_clk_frequency())
}

/// Switch the CPU clock at runtime.
///
/// Reuses [`ClockConfig::configure`], which applies only the diff from the current clock
/// tree, so a switch between two presets sharing a PLL is divider-only (no recalibration).
/// Does not change core voltage: lowering the clock is safe; raising it above what the
/// boot-time voltage sustains may be unstable.
pub fn set_cpu_clock(clock: CpuClock) {
ClockTree::with(|clocks| {
ClockConfig::from(clock).configure(clocks);
});
}

/// Performance level for [`PerfLock`]-based dynamic frequency scaling.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PerfLevel {
/// Resting, power-efficient clock.
Base,
/// On-demand high-performance clock.
High,
}

struct PerfState {
base: CpuClock,
high: CpuClock,
high_refs: usize,
}

// Defaults to `max()` so an unconfigured system never scales down.
static PERF: esp_sync::NonReentrantMutex<PerfState> =
esp_sync::NonReentrantMutex::new(PerfState {
base: CpuClock::max(),
high: CpuClock::max(),
high_refs: 0,
});

/// Set the `Base` (resting) and `High` (on-demand) clocks for [`PerfLock`].
///
/// Pick a `high` sharing `base`'s PLL to keep the switch divider-only. Does not change
/// the current clock; both default to [`CpuClock::max`] until set.
pub fn set_perf_levels(base: CpuClock, high: CpuClock) {
PERF.with(|s| {
s.base = base;
s.high = high;
});
}

/// Refcounted request to run at a [`PerfLevel`] until dropped.
///
/// The CPU runs at `High` while at least one `PerfLock(High)` is alive and returns to
/// `Base` when the last is dropped; `Base` requests are inert. The refcount and the clock
/// switch update under one critical section, so requests from either core stay consistent.
#[must_use = "the performance level is released when the PerfLock is dropped"]
#[non_exhaustive]
pub struct PerfLock {
holds_high: bool,
}

impl PerfLock {
/// Request `level` until the returned guard is dropped.
pub fn request(level: PerfLevel) -> Self {
let holds_high = level == PerfLevel::High;
if holds_high {
PERF.with(|s| {
s.high_refs += 1;
if s.high_refs == 1 {
set_cpu_clock(s.high);
}
});
}
Self { holds_high }
}

/// The level currently in effect.
pub fn current_level() -> PerfLevel {
PERF.with(|s| {
if s.high_refs > 0 {
PerfLevel::High
} else {
PerfLevel::Base
}
})
}
}

impl Drop for PerfLock {
fn drop(&mut self) {
if self.holds_high {
PERF.with(|s| {
s.high_refs -= 1;
if s.high_refs == 0 {
set_cpu_clock(s.base);
}
});
}
}
}

/// The XTAL clock frequency.
pub fn xtal_clock() -> Rate {
Rate::from_hz(ll::xtal_clk_frequency())
Expand Down
15 changes: 15 additions & 0 deletions esp-hal/src/exception_handler/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
use crate::trapframe::TrapFrame;

/// Last CPU exception, for reading over a debug probe: `[cpu, exccause, pc, excvaddr]`.
#[cfg(xtensa)]
#[unsafe(no_mangle)]
pub static ESP_HAL_LAST_EXCEPTION: [portable_atomic::AtomicU32; 4] =
[const { portable_atomic::AtomicU32::new(0) }; 4];

#[cfg(xtensa)]
#[unsafe(no_mangle)]
#[unsafe(link_section = ".rwtext")]
unsafe extern "C" fn __user_exception(
cause: xtensa_lx_rt::exception::ExceptionCause,
context: &TrapFrame,
) {
// Bench aid: keep the raw exception details readable over JTAG after the panic halts
// the chip (the panic printer may drop the message): [cpu, cause, pc, excvaddr].
{
use portable_atomic::Ordering::Relaxed;
ESP_HAL_LAST_EXCEPTION[0].store(crate::system::Cpu::current() as u32, Relaxed);
ESP_HAL_LAST_EXCEPTION[1].store(context.EXCCAUSE, Relaxed);
ESP_HAL_LAST_EXCEPTION[2].store(context.PC, Relaxed);
ESP_HAL_LAST_EXCEPTION[3].store(context.EXCVADDR, Relaxed);
}
panic!(
"\n\nException occurred on {:?} '{:?}'\n{:?}",
crate::system::Cpu::current(),
Expand Down
39 changes: 39 additions & 0 deletions esp-hal/src/rtc_cntl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -781,6 +781,10 @@ cfg_select! {
}
}

// Absolute time (microseconds since boot) past which automatic light sleep is refused until
// cleared; `u64::MAX` means no deadline. See `WakeLock::set_sleep_deadline`.
static SLEEP_DEADLINE_US: portable_atomic::AtomicU64 = portable_atomic::AtomicU64::new(u64::MAX);

/// A guard that prevents the system from entering automatic light sleep.
///
/// While at least one `WakeLock` is held, [`WakeLock::is_active`] returns `true`
Expand Down Expand Up @@ -821,6 +825,41 @@ impl WakeLock {
}
}

/// Returns the number of wake locks currently held.
pub fn holders() -> usize {
cfg_select! {
sleep_light_sleep => wake_lock_count().load(portable_atomic::Ordering::Acquire),
_ => 0,
}
}

/// Allows automatic light sleep only until `deadline`, then holds the chip awake.
///
/// A driver that released its wake lock for a bounded gap (a radio controller in modem
/// sleep) uses this to make the idle hook wake the chip before the gap ends. The hook
/// sleeps at most until the deadline, and refuses to sleep once the deadline has passed
/// until [`Self::clear_sleep_deadline`] is called. This mirrors the ESP-IDF pattern of a
/// wakeup timer that re-takes the power-management lock before the controller wakes.
pub fn set_sleep_deadline(deadline: crate::time::Instant) {
SLEEP_DEADLINE_US.store(
deadline.duration_since_epoch().as_micros(),
portable_atomic::Ordering::Release,
);
}

/// Removes the deadline set by [`Self::set_sleep_deadline`].
pub fn clear_sleep_deadline() {
SLEEP_DEADLINE_US.store(u64::MAX, portable_atomic::Ordering::Release);
}

/// Returns the current sleep deadline, if one is set.
pub fn sleep_deadline() -> Option<crate::time::Instant> {
match SLEEP_DEADLINE_US.load(portable_atomic::Ordering::Acquire) {
u64::MAX => None,
us => Some(crate::time::Instant::EPOCH + crate::time::Duration::from_micros(us)),
}
}

/// Returns `true` if at least one wake lock is currently held.
#[instability::unstable]
pub fn is_active() -> bool {
Expand Down
18 changes: 18 additions & 0 deletions esp-hal/src/rtc_cntl/sleep/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,24 @@ mod timer;
mod wakeup;
pub(crate) use wakeup::*;

/// Lets the Bluetooth controller wake the chip from light sleep.
///
/// A radio driver that lets the chip light-sleep between controller events enables this, so
/// controller activity ends the sleep. Deep sleep powers the controller down, so this source
/// ends light sleep only.
#[cfg(any(esp32c3, esp32s3))]
#[instability::unstable]
pub fn enable_bt_wakeup() {
WakeupSource::Bt.enable();
}

/// Stops the Bluetooth controller from waking the chip.
#[cfg(any(esp32c3, esp32s3))]
#[instability::unstable]
pub fn disable_bt_wakeup() {
WakeupSource::Bt.disable();
}

/// Prepares the sleep hardware, and clears the wakeup sources of the previous run.
///
/// The wakeup-enable mask survives a deep-sleep wake, so here it still holds the request of the run
Expand Down
19 changes: 13 additions & 6 deletions esp-hal/src/uart/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -640,7 +640,7 @@ where
guard: rx_guard,
peri_clock_guard: peri_clock_guard.clone(),
// Receiving data continuously, the peripheral can't let the system sleep.
_wake_lock: WakeLock::new(),
wake_lock: Some(WakeLock::new()),
reported_errors: config.rx.reported_errors,
},
tx: UartTx {
Expand Down Expand Up @@ -698,8 +698,9 @@ pub struct UartRx<'d, Dm: DriverMode> {
phantom: PhantomData<Dm>,
guard: PeripheralGuard,
peri_clock_guard: UartClockGuard<'d>,
// Receiving data continuously, the peripheral can't let the system sleep.
_wake_lock: WakeLock,
// Receiving data continuously, the peripheral can't let the system sleep, unless the RX
// line is a wakeup source (see `enable_wakeup`), in which case the lock is dropped.
wake_lock: Option<WakeLock>,
reported_errors: EnumSet<RxErrorKind>,
}

Expand Down Expand Up @@ -1178,7 +1179,7 @@ impl<'d> UartRx<'d, Blocking> {
phantom: PhantomData,
guard: self.guard,
peri_clock_guard: self.peri_clock_guard,
_wake_lock: self._wake_lock,
wake_lock: self.wake_lock,
reported_errors: self.reported_errors,
}
}
Expand All @@ -1201,7 +1202,7 @@ impl<'d> UartRx<'d, Async> {
phantom: PhantomData,
guard: self.guard,
peri_clock_guard: self.peri_clock_guard,
_wake_lock: self._wake_lock,
wake_lock: self.wake_lock,
reported_errors: self.reported_errors,
}
}
Expand Down Expand Up @@ -1443,14 +1444,20 @@ where
#[cfg(sleep_driver_supported)]
#[instability::unstable]
pub fn enable_wakeup(&mut self, config: &WakeupConfig) -> Result<(), WakeConfigError> {
self.uart.info().enable_wakeup(config)
self.uart.info().enable_wakeup(config)?;
// The RX line now wakes the chip itself, so the receiver no longer needs to keep it awake.
self.wake_lock = None;
Ok(())
}

/// Stops the UART from waking the chip.
#[cfg(sleep_driver_supported)]
#[instability::unstable]
pub fn disable_wakeup(&mut self) {
self.uart.info().disable_wakeup();
if self.wake_lock.is_none() {
self.wake_lock = Some(WakeLock::new());
}
}

/// Reads and clears RX error conditions set by received data.
Expand Down
Loading