fix(esp-hal): avoid dividing by a failed RTC clock calibration when sleeping - #6019
Closed
jphein wants to merge 1 commit into
Closed
fix(esp-hal): avoid dividing by a failed RTC clock calibration when sleeping#6019jphein wants to merge 1 commit into
jphein wants to merge 1 commit into
Conversation
…leeping `RtcClock::calibrate` returns 0 when the TIMG calibration times out (it already logs "calibration failed"). That 0 is stored verbatim and read back by `rtc_slow_cal_period`/`rtc_fast_cal_period`, so `SleepTimeConfig::us_to_slowclk`/`us_to_fastclk` divided by zero and panicked. Entering light or deep sleep therefore aborted the firmware on any board where the slow clock does not start - for example a board selecting XTAL32K with a missing or badly loaded 32.768 kHz crystal, which is exactly the failure the calibration timeout exists to detect. Fall back to the period implied by the clock's nominal frequency when calibration is unavailable, and log a warning. Sleep timing is then as accurate as the uncalibrated oscillator, which is a far better outcome than a panic. Fixes esp-rs#6016
Contributor
There was a problem hiding this comment.
Pull request overview
This PR hardens the PMU sleep timing calculations in esp-hal by preventing a divide-by-zero when RTC clock calibration returns 0 (timeout / unavailable). Instead of panicking on sleep entry, it falls back to a nominal clock period derived from the configured clock source’s nominal frequency and emits a warning.
Changes:
- Added a small helper (
nominal_cal_period) to compute a fixed-point “µs per cycle” period in the same representation asRtcClock::calibrate. - Guarded
rtc_fast_cal_period()/rtc_slow_cal_period()results and substituted a nominal period when the stored calibration period is0. - Logged a warning when falling back to nominal periods for RC_FAST or RTC_SLOW.
Contributor
|
IIRC ESP-IDF does the same what we do on main (i.e. they assert on period and fail on divide by zero in non-debug) |
Contributor
|
If we fail the calibration, we shouldn't assume nominal frequency because something is wrong with the setup somewhere. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
RtcClock::calibratereturns0when the TIMG calibration times out. That is a documented outcome, not a corner case — the doc comment oncalibratecalls it out explicitly:The
0is stored verbatim (calibrate_rtc_slow_clockwrites it toLP_AON.store1,calibrate_rtc_fast_clocktoRC_FAST_CAL_VAL) and read back unmodified byrtc_slow_cal_period/rtc_fast_cal_period.SleepTimeConfigthen divides by it:So on a board where the slow clock does not start — precisely the situation the calibration timeout exists to detect — the firmware logs
"... calibration failed"and then panics with a divide-by-zero on the next attempt to enter light or deep sleep.us_to_slowclk/us_to_fastclkare on the unconditional path of every PMU-chip sleep entry (pmu_sleep_calculate_hw_wait_time, theparam.hp_sys/param.lp_syssetup inesp32c5.rs,esp32c6.rs,esp32c61.rs,esp32h2.rs,esp32p4.rs), so there is no way to reach sleep without hitting it.This substitutes the period implied by the clock's nominal frequency when calibration is unavailable, and logs a warning. Sleep timing is then only as accurate as the uncalibrated oscillator, which is a much better outcome than aborting the firmware.
Fixes #6016
On the choice of fallback value
The fallback must be in the same representation
calibratereturns: microseconds per clock cycle, as a fixed-point number withRtcClock::CAL_FRACT(19) fractional bits.calibratealready computes exactly that from a known frequency in itsXtal32kClkshort-circuit:nominal_cal_periodis that same expression, so a fallback is numerically identical to what a successful calibration of a perfectly nominal oscillator would have produced.The two frequencies:
calibrate_rtc_fast_clockcalibratesTimgCalibrationClockConfig::RcFastDivClk, whose clock-tree frequency isrc_fast_clk_frequency(). Where a chip revision uses the divided calibration tick,measure_rtc_clockdivides the requested cycle count by the same divider before measuring andcalibratedivides by the undividedslowclk_cycles, so the result is the period of RC_FAST itself either way. Nominal: 17.5 MHz (C6/C61), 20 MHz (C5/P4), 8 MHz (H2).RtcClock::slow_freq()is documented as "the nominal value of the RTC_SLOW_CLK source" and resolves the currently configured source (RC_SLOW / XTAL32K / OSC_SLOW), which is the same sourcecalibrate_rtc_slow_clockselects. Nominal: 136 kHz for RC_SLOW on C6.Two alternatives were considered and not taken:
Optionfrom the sleep entry points. More correct in principle, but it is an API break across everysleep_*entry point, and it forces callers to handle a case that is unrecoverable at that point anyway. Worth doing separately if maintainers prefer it.0before the first successful calibration, so this does not cover the first-sleep case, which is the one users actually hit.Happy to switch to either if that is the preference — the shape of the guard would be the same.
Testing
cargo checkandcargo clippy(withunstable, and withunstable,defmt) are clean for every chip that compilespmu_common.rs— C5, C6, C61, H2 onriscv32imac-unknown-none-elfand P4 onriscv32imafc-unknown-none-elf— andcargo checkis also clean for C6 withoutunstable.I do not have a board that reproduces a calibration timeout, so the failure path itself is argued from the code rather than observed on hardware. The happy path is unchanged: a non-zero calibration result is returned exactly as before, byte for byte.
Numerically, the fallbacks come out as: 29959 for RC_FAST at 17.5 MHz, 26214 at 20 MHz, 65536 at 8 MHz, and 3855058 for RC_SLOW at 136 kHz. Round-tripping the last one through
slowclk_to_usgives3855058 >> 19 = 7 µsper cycle, matching 1/136 kHz = 7.35 µs.Changelog
esp-hal