Skip to content

kernel::power

Power management: sleep, and reconciling the kernel tick on wake.

The chip-level sleep FSM is reached through the [hal::power::LowPower] seam, selected with the SoC (board::SelectedSoc) exactly as [crate::clock] selects its counter; this module is the kernel half — it drives that primitive and, crucially, keeps the scheduler’s clock honest across a light sleep, naming no chip to do it.

The system tick is driven by the Xtensa CCOUNT/CCOMPARE0 timer, which counts CPU cycles. Light sleep gates the CPU clock, so CCOUNT — and the Timer0 interrupt that increments the tick — both freeze for the whole sleep. On wake the kernel’s notion of time is behind reality by exactly the time slept. Left alone, every sleep_ms, timeout and timestamp taken after the sleep would be wrong, and a task that slept “until now + 100 ms” would wake far too late.

The RTC slow clock keeps running through light sleep, so the SoC primitive measures the real elapsed time from it and hands it back. This module turns that into whole tick periods and adds them — [ticks_elapsed] — so the tick catches up. It only ever adds: the counter cannot go backwards, no matter what the measurement returns.

  • Deep sleep returns nothing to reconcile — it wakes as a reset, so the tick restarts from zero with the rest of the system. deep_sleep is here for completeness and is exercised by construction, not by the self-test (it would reset the board mid-run).
  • Auto light sleep from the idle task (the waiti hook) is a follow-up; this cut only offers an explicit call.
  • Non-timer wake sources (RTC-GPIO, touch, ULP) are a follow-up in the SoC layer.
fn ticks_elapsed(elapsed_us: u64, period_us: u32) -> u64

Whole tick periods in elapsed_us, for reconciling the tick after a sleep.

Rounds down: a residue shorter than one period cannot advance a whole-tick counter, and rounding up would let repeated short sleeps run the clock fast. The lost sub-tick remainder is bounded by one period (1 ms on every current board) and does not accumulate across calls, because each sleep measures its own true elapsed time from the RTC rather than trusting the requested duration.

period_us of zero — the tick timer never initialised — yields zero rather than dividing by zero, so a reconciliation before boot completes is a no-op instead of a fault.