[RFC] sys/ztimer: power management for the different clocks#13585
[RFC] sys/ztimer: power management for the different clocks#13585jue89 wants to merge 2 commits intoRIOT-OS:masterfrom
Conversation
|
|
||
| /* if this is the first timer for this clock, acquire it */ | ||
| if (clock->ops->acquire && clock->list.next == NULL) { | ||
| clock->ops->acquire(clock); | ||
| } | ||
|
|
There was a problem hiding this comment.
If I understand ztimer correctly, this is the best place to put this bit if we do not want to lose accuracy when setting up new timers. But in this case clock->ops->now() is called before clock->ops->acquire(). This is not harmful if we are just interacting with pm_layered but could become a problem in the future if we actually want to power on the timer using this callback.
|
May I ask you if the overall concept is acceptable and heading in the right direction, @kaspar030? I would like to enhance the new ztimer regarding power saving features. Its concepts are really well-fitting to my application and would remove some quirks I was forced to introduce ;) |
|
@jue89, thanks for working on this!
It might, but I'd like to get some evaluation before pursuing this. I think, as a first step, we should consider the HF clock running while not in deep sleep and stopped while in deep sleep. On most platforms, that's the case anyways. In those cases, we just need to add blocking a PM mode, in ztimer_periph_timer, as soon as a time is set (and unblocking when the llist becomes empty). This shouldn't be more than a couple of lines. For platforms that don't deep-sleep as long as the HF timer is running (I think that would be at least nrf52), I'd rather, as first step, add pm_layered hooks and use them stop the timer on entering sleep, and restore it on waeking up. That is probably also quite easy to implement in a generic way. At that point, we should have deep sleep power consumption down quite a lot. This could be the most important metric. Also, this would be doable without requiring any user API changes, with almost no added complexity, without changing on how timers can be used and most of it contained to ztimer or the pm layer. At that point, we need proper measurements on the extra power use of a permanently active HF clock vs. one that's off, on multiple platforms. If we're lucky, it doesn't really matter, as in active mode, the CPU clock is running full-tilt anyways, and another running counter might be barely noticable. My second preferred outcome would be a clear sign that most HF timer hardware uses substantial power when activated, so we know for sure that we need to disable them as much as possible. It might turn out to be a tough call on whether any added complexity is worth it for saving a couple of percent of power in active mode, especially as it depends on the application how much of the time a node is sleeping anyways. But I really think we need numbers to decide this. |
|
Thank you for your reply!
I tried to implement this PR by touching just This forced me to extend
Yip, full ack.
That is the reason why I started working on As a bonus of the assumption you described above, a device can check how long it has been in deep-sleep by comparing ZTIMER_MSEC and ZTIMER_USEC :) This is really helpful during development since the developer will be warned if the device wasn't sleeping at all.
I had a deep-dive into the SAML21 family and the difference between the running and the stopped HF clock is a current reduction from 400µA to 3µA. So for my use-case (it's a battery-driven device) switching off the HF clock is substantial for sufficient battery life. A colleague of mine has experiences with the nRF52 family and he told me about similar power savings.
Sure. But I haven't seen a IoT battery-driven application that isn't sleeping most of the day, yet ;) |
No, not at all. I understood that the acquire / release is used to enable/disable the clock whenever it is not needed by applications (as in, acquire / release by them), instead of by ztimer itself. Can we get around that by, instead of doing acquire/release in all the hot code paths, adding ztimer_pause()/ztimer_resume(), once, to the generic ztimer_clock?
ztimer_stop() would then be called by pm hooks on deep sleep enter, for specific clocks only. Hm, that still requires clock specific ops. And it turns the supposed-to-be-easy stuff into ztimer's design.
Yeah, but the HF clock is also the one that drives the CPU. So while that is active anyways, running the timer hardware on top should be cheap. But I think turning off HF timer hardware in deep sleep was actually your intention, right? |
|
BTW, let's use "ztimer clock" if referring to that, and "clock" when referring to MCU clocks (which might drive timer hardware). I've used only "clock" before for both, I guess that causes confusion. edit clarified |
| .set = _ztimer_periph_rtt_set, | ||
| .now = _ztimer_periph_rtt_now, | ||
| .cancel = _ztimer_periph_rtt_cancel, | ||
| .acquire = NULL, |
There was a problem hiding this comment.
these lines are not needed, C implicitly initializes them to 0, which for all we know means NULL.
To be honest, I don't like that idea. The pm hooks are called, once the user application tells the pm to go into deep sleep. So the user application (and all other parts of RIOT) must be aware that a certain pm state must be blocked or unblocked to use ZTIMER_USEC. I would state the following logical chains: setting a ztimer clockThe user requires a high precision ztimer clock (i.e. ZTIMER_USEC) => ztimer requires the underlying hardware clock (i.e. periph_timer) => the underlying hardware clock requires the CPU to be within a certain pm state => block(certain pm state) ztimer reaches the target of the last ztimer clockThe hardware timer fires an interrupt => periph_timer tells ztimer the target has been reached => ztimer realizes that no other ztimer clocks are depending on periph_timer => ztimer releases periph_timer => unblock(certain pm state)
The CPU core isn't clocked at all during deep-sleep. The only living clock is the RTC/RTT. It will wake the whole system (including the HF clock), once it fires an interrupt. (The neat part of ZTIMER_MSEC sourced by the RTC/RTT. It just works ;)) To be clear: I want to have an easy-to-use OS that aims for the lowest power consumption as possible without requiring the user to understand every little aspect of the power management system. |
I was thinking that in addition to pm_layered ztimer_pause()/ztimer_resume(), the actual ztimer clocks block/unblock their power mode. If we move that code to the generic ztimer_clock code, it can be done in the extension clock, so the extended clock doesn't even care. That way we accomplish automatic power down (if necessary) for HF ztimer clocks if the platform doesn't I'm thinking this, concretely: Case 1: e.g., samr21-xpro, where the HF timer clock stops when entering deep sleep. No extension. Case 2: e.g., nrf51, HF needs special handling (disabling), ztimer_extend extends 24bit to 32bit. The application is completely unaware. As long as it has an active timer, ztimer_extend would block the right power mode. If there's no active timer, the ztimer clock would stop counting on entering deep sleep, but continue counting once back to active mode. |
|
Oh I got you wrong, sry. It's a little bit hard to understand implementation ideas just by reading text that is not code :D Hopefully, I got you right this time. I try to describe what I understood regarding the central logic of the interaction with pm_layered: Once a Once a Am I right? |
Yes! (Only the first added or last removed trigger Does that make sense? |
|
Yip, I think I can come up with an implementation of your proposed approach. I think it makes sense to open another PR and close this one?! So both implementations still can be compared and tested. |
|
Let's move the discussion to #13722. |
Contribution description
ztimer has been designed to handle different clock sources and picks the right one for a timeout requested by the users.
This has the potential to handle power management seamlessly! Since clocks with higher frequencies tend to consume more power than those with lower frequencies, we can react according to the currently demanded clocks.
If a high frequency clock has no timers to handle, we cloud disable it to save energy.
This, of course, comes with a cost:
timer_now()calls that base on the high frequency clock stuck while it is disabled.This PR introduces a callback approach to notify whether a clock is actually required by a timer or not. It is more meant to be a basis for discussion. Maybe we can develop a solution that fits best: Power saving but also the smallest impact on the accuracy.
Testing procedure
Pick a board that disables the high frequency timers in a certain PM level. Unblock all power levels including that one. Set the CFLAG
CONFIG_ZTIMER_PERIPH_TIMER_PM_LAYER=[the certain PM level].That certain PM level should be blocked/unblocked depending whether a timer based on
ZTIMER_USECis running.If the proposed concept is accepted, I will add a test for this case.
Issues/PRs references
Closes #13580