Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ztimer: add ztimer_ondemand module for implicit power management #17607

Merged
merged 13 commits into from
Dec 6, 2022

Conversation

jue89
Copy link
Contributor

@jue89 jue89 commented Feb 2, 2022

Contribution description

This PR implements the approach proposed in #16327. Basically it extends ztimer to start and stop ztimer_periph_% depending on the count of currently active users. This solves problems like described in #16891.

The ztimer_ondemand feature can be enabled per peripheral driver. To start/stop periph_timer, ztimer_ondemand_timer has to be pulled in. periph_rtt is started/stopped by ztimer_ondemand_rtt and periph_rtc by ztimer_ondemand_rtc. I think you get the idea ;-)

To help ztimer to understand if someone is currently relaying on a certain ztimer_clock_t, ztimer_acquire() and ztimer_release() have been introduced. These methods are required for users of ztimer_now(). The interface works like this:

ztimer_acquire(ZTIMER_USEC);
ztimer_now_t start = ztimer_now(ZTIMER_USEC);
do_some_work();
ztimer_now_t duration = ztimer_now(ZTIMER_USEC) - start;
ztimer_release(ZTIMER_USEC);

(ztimer_set() and ztimer_remove() will call ztimer_acquire() and ztimer_release() internally.)

First of all, I'd love to hear your opinion about the direction I'm heading in.

I also came up with a possible migration path: #17607 (comment)

Testing procedure

I've added unit tests.

During the next days I'll come up with some sample code that you may run on your device.

A demo showcasing ztimer_ondemand:

Diff for the demo rebased on current master
diff --git a/cpu/efm32/periph/pm.c b/cpu/efm32/periph/pm.c
index 9c60448cef..10ac8017dc 100644
--- a/cpu/efm32/periph/pm.c
+++ b/cpu/efm32/periph/pm.c
@@ -22,7 +22,7 @@
 
 #include "em_emu.h"
 
-#define ENABLE_DEBUG 0
+#define ENABLE_DEBUG 1
 #include "debug.h"
 
 void pm_set(unsigned mode)
diff --git a/cpu/saml21/periph/pm.c b/cpu/saml21/periph/pm.c
index 26443e8346..b8711ac993 100644
--- a/cpu/saml21/periph/pm.c
+++ b/cpu/saml21/periph/pm.c
@@ -21,7 +21,7 @@
 
 #include "periph/pm.h"
 
-#define ENABLE_DEBUG 0
+#define ENABLE_DEBUG 1
 #include "debug.h"
 
 void pm_set(unsigned mode)
diff --git a/examples/hello-world/Makefile b/examples/hello-world/Makefile
index 258d8e9baf..69a76ca858 100644
--- a/examples/hello-world/Makefile
+++ b/examples/hello-world/Makefile
@@ -15,4 +15,7 @@ DEVELHELP ?= 1
 # Change this to 0 show compiler invocation lines by default:
 QUIET ?= 1
 
+USEMODULE += ztimer ztimer_usec ztimer_msec
+USEMODULE += ztimer_ondemand ztimer_ondemand_strict ztimer_ondemand_timer ztimer_ondemand_rtt ztimer_ondemand_rtc
+
 include $(RIOTBASE)/Makefile.include
diff --git a/examples/hello-world/main.c b/examples/hello-world/main.c
index f51bf8c0a0..30a48d638c 100644
--- a/examples/hello-world/main.c
+++ b/examples/hello-world/main.c
@@ -20,6 +20,13 @@
  */
 
 #include <stdio.h>
+#include "ztimer.h"
+
+static void cb(void* arg)
+{
+    (void) arg;
+    puts("Going to sleep ...");
+}
 
 int main(void)
 {
@@ -28,5 +35,12 @@ int main(void)
     printf("You are running RIOT on a(n) %s board.\n", RIOT_BOARD);
     printf("This board features a(n) %s MCU.\n", RIOT_MCU);
 
+    ztimer_t timer = {.callback = cb};
+    ztimer_set(ZTIMER_USEC, &timer, 1000000);
+
+    ztimer_sleep(ZTIMER_MSEC, 5000);
+
+    puts("Bye!");
+
     return 0;
 }

Some tests on boards that already have power management on periph_timer and periph_rtt:

samr30-xpro:

make -C examples/hello-world/ BOARD=same30-xpro flash term
2022-11-14 12:50:26,431 # main(): This is RIOT! (Version: 2023.01-devel-358-g21db3-feature/ztimer-ondemand)
2022-11-14 12:50:26,433 # Hello World!
2022-11-14 12:50:26,437 # You are running RIOT on a(n) samr30-xpro board.
2022-11-14 12:50:26,440 # This board features a(n) saml21 MCU.
2022-11-14 12:50:26,443 # pm_set(): setting IDLE mode.
2022-11-14 12:50:27,443 # Going to sleep ...
2022-11-14 12:50:27,445 # pm_set(): setting STANDBY mode.
2022-11-14 12:50:31,425 # Bye!
2022-11-14 12:50:31,428 # pm_set(): setting BACKUP mode.

xg23-pk6068a:

make -C examples/hello-world/ BOARD=xg23-pk6068a flash term
2022-11-14 12:47:56,154 # main(): This is RIOT! (Version: 2023.01-devel-246-g0dd56-feature/ztimer-ondemand)
2022-11-14 12:47:56,155 # Hello World!
2022-11-14 12:47:56,157 # You are running RIOT on a(n) xg23-pk6068a board.
2022-11-14 12:47:56,157 # This board features a(n) efm32 MCU.
2022-11-14 12:47:56,157 # [pm] enter EFM32_PM_MODE_EM1
2022-11-14 12:47:56,215 # Going to sleep ...
2022-11-14 12:47:56,216 # [pm] enter EFM32_PM_MODE_EM2
2022-11-14 12:48:00,214 # Bye!
2022-11-14 12:48:00,216 # [pm] enter EFM32_PM_MODE_EM3

nrf52dk:

make -C examples/hello-world/ BOARD=nrf52dk flash term
(power cycle to make sure the debugger isn't attached to the MCU)
2022-11-22 20:09:24,384 # main(): This is RIOT! (Version: 2023.01-devel-444-gade0b-feature/ztimer-ondemand)
2022-11-22 20:09:24,385 # Hello World!
2022-11-22 20:09:24,389 # You are running RIOT on a(n) nrf52dk board.
2022-11-22 20:09:24,392 # This board features a(n) nrf52 MCU.
2022-11-22 20:09:25,384 # Going to sleep ...
(drawn current changes from 400uA to 15uA)
2022-11-22 20:09:29,393 # Bye!

They're all entering lowest power mode after all the hard hello world work is done \o/

Issues/PRs references

@jue89 jue89 added Process: API change Integration Process: PR contains or issue proposes an API change. Should be handled with care. Area: timers Area: timer subsystems labels Feb 2, 2022
@github-actions github-actions bot added Area: sys Area: System Area: tests Area: tests and testing framework labels Feb 2, 2022
@jue89 jue89 requested a review from kfessel February 2, 2022 21:23
@jue89
Copy link
Contributor Author

jue89 commented Feb 2, 2022

Sneak peak: I'm completely aware of ztimer_acquire() and ztimer_release() introducing more complexity. But this interface could be just a foundation for something like this:

ztimer_watch
typedef struct {
    ztimer_now_t checkpoint; /**< last seen timer now value */
    ztimer_now_t value;      /**< watch time at checkpoint */
    bool running;            /**< flag showing if the watch is running */
} ztimer_watch_t;

void ztimer_watch_start(ztimer_clock_t *clock, ztimer_watch_t *watch)
{
    if (watch->running) {
        return;
    }

    ztimer_acquire(clock);
    watch->checkpoint = ztimer_now(clock);
    running = true;
}

void ztimer_watch_set(ztimer_clock_t *clock, ztimer_watch_t *watch, ztimer_now_t val)
{
    if (watch->running) {
        watch->checkpoint = ztimer_now(clock);
    }

    watch->value = val;
}

ztimer_now_t ztimer_watch_read(ztimer_clock_t *clock, ztimer_watch_t *watch)
{
    if (watch->running) {
        ztimer_now_t now = ztimer_now(clock);
        return now - watch->checkpoint + watch->value;
    }
    else {
        return watch->value;
    }
}

void ztimer_watch_stop(ztimer_clock_t *clock, ztimer_watch_t *watch)
{
    if (!watch->running) {
        return;
    }

    watch->value = ztimer_watch_read(clock, watch);
    watch->running = false;
    ztimer_release(clock);
}

We should make every user of ztimer_now() migrate to an interface as shown above.

Or we could introduce a global ztimer_watch_t uptime that is backed by ZTIMER_MSEC and started during auto_init. For time measurement, one may call ztimer_watch_read(ZTIMER_MSEC, &uptime).

@chrysn
Copy link
Member

chrysn commented Feb 3, 2022

Is this really an API change? The use of ztimer_now has a pending PR #17298 documenting previously silent assumptions made; I think that's the real API chan^Wfix here, whereas this PR just makes ztimer use its potential.

On acquire and release, I wonder if they are actually relevant (and warrant their counter). Any user just comparing the ztimer_now outputs will find they have a hard time knowing whether it wrapped, whereas if someone acquiring the ztimer just placed a maximal timeout and then checked for it still being set after obtaining a later now value would be sure that it did not wrap (or, it may have wrapped but the wrapping difference never overflew). Their timer callback could be a no-op (the task will conclude, and the measured time will be "exceeding the scale"), or do some cancellation ("don't bother, it has already taken too long").

(The uptime counter would need special handling then, though.)

@jue89
Copy link
Contributor Author

jue89 commented Feb 3, 2022

Thank you for your response!

Yes, I agree that setting up a ztimer_t with a very long offset can also be a solution to make the pm_layered approach work. But it feels more like a workaround than a proper solution. TBH, I'm regretting that I've introduced this approach in the first place. It seemed to be easy and elegant one but I've shot myself into the foot several times :D

You may have noticed: This PR disables all these mechanics, once you pull in ztimer_ondemand (I'm still not sure if this name is verbose enough ... but I wasn't able to come up with a better one, yet.) Once we found a working solution that doesn't require any workarounds, I'd say we should introduce pm_layered into all periph_timer implementation and then deprecate the mechanics around direct interaction between ztimer and pm_layered.

After having made my first baby steps with this approach, I've already noticed that this implicit power management fails fast, which is a good thing! If a ztimer_acquire() is missing, you'll notice pretty fast because the periph_timer is disabled even if the MCU is still on fire and hasn't entered any power-saving modes, yet. I'd say those bugs that show themselves only by accident are really annoying type of bugs. (I.e. you changed timing of your application and, thus, different pm modes are entered at different times and see stuff like networking starting to behave weirdly. This will happen if ztimer_now() is used to time stuff - and it is!)

@jue89
Copy link
Contributor Author

jue89 commented Feb 3, 2022

An addition to the wrapped ztimer_now() values - I just understood your point after reading your response a second time: If there must the guarantee that it hasn't wrapped during time measurement, we would have to use ztimer_now64. That should create enough room that timer_now() never wraps.

Your approach clearly detects wraps: But what shall we do if we detected wrapping timers? Extend like ztimer_now64 does?

@chrysn
Copy link
Member

chrysn commented Feb 3, 2022 via email

Copy link
Contributor

@kfessel kfessel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe i am missing something but i dont thing you need to countd up and down for every timer

sys/ztimer/core.c Outdated Show resolved Hide resolved
@jue89
Copy link
Contributor Author

jue89 commented Feb 4, 2022

Some additional work: I've introduced ztimer_ondemand_strict with 04607ed. It will make sure ztimer_now() is only called if the timer is active. Of course, this pseudomodule brakes almost every module using ztimer. But it already showed me that I used ztimer_now() in my patch before turning on the clock! This has been fixed with ba77e7b.

Furthermore, I wrote a little example: tests/ztimer_ondemand for you to run on your boards :)

@jue89
Copy link
Contributor Author

jue89 commented Feb 4, 2022

Would anybody mind if I squash commits?

I think I have finished for the first round and implemented everything necessary for the ztimer_ondemand driver to work properly.

@chrysn
Copy link
Member

chrysn commented Feb 4, 2022

I wouldn't mind, and I think that kfessel's comment is high-level enough that a squash wouldn't disturb it.

@jue89 jue89 force-pushed the feature/ztimer-ondemand branch 2 times, most recently from 371045e to 309cbfe Compare February 7, 2022 12:42
@jue89
Copy link
Contributor Author

jue89 commented Feb 7, 2022

And squashed. I'd say this PR is ready to be reviewed.

I've fixed typos and added some breaks to long lines. Static tests are green now.

@jue89 jue89 added the CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR label Feb 15, 2022
@jue89
Copy link
Contributor Author

jue89 commented Nov 14, 2022

I updated the initial PR message with my testing procedure on already working boards and families :)

I'd say this is ready to be reviewed.

Copy link
Member

@maribu maribu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. Some comments with suggestions inline.

sys/include/ztimer.h Outdated Show resolved Hide resolved
sys/include/ztimer.h Outdated Show resolved Hide resolved
Comment on lines 453 to 459
unsigned state = irq_disable();
if (_ztimer_acquire(clock) == true) {
/* if the clock just has been enabled, make sure to set possibly
* required checkpoints for clock extension */
_ztimer_update(clock);
}
irq_restore(state);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it make sense to move all the IRQ management and the call to _ztimer_update() into _ztimer_acquire() to safe a bit of ROM?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather remove the inline statement and move ztimer_acquire() into core.c.

The separation between ztimer_acquire() and _ztimer_acquire() saves some time: the _ztimer_update() call on the first acquisition isn't required if it is called by ztimer_set() ... it'll set the right timer in the process.

Or am I underestimating the time required for a function call?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope, I got you right ... I added two fixup commits.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you have another look onto the code if I understood your suggestion correctly?

sys/include/ztimer.h Outdated Show resolved Hide resolved
sys/ztimer/core.c Outdated Show resolved Hide resolved
Comment on lines +77 to +89
#if MODULE_ZTIMER_ONDEMAND_RTT
static void _ztimer_periph_rtt_start(ztimer_clock_t *clock)
{
(void)clock;
rtt_poweron();
}

static void _ztimer_periph_rtt_stop(ztimer_clock_t *clock)
{
(void)clock;
rtt_poweroff();
}
#endif /* MODULE_ZTIMER_ONDEMAND_RTT */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#if MODULE_ZTIMER_ONDEMAND_RTT
static void _ztimer_periph_rtt_start(ztimer_clock_t *clock)
{
(void)clock;
rtt_poweron();
}
static void _ztimer_periph_rtt_stop(ztimer_clock_t *clock)
{
(void)clock;
rtt_poweroff();
}
#endif /* MODULE_ZTIMER_ONDEMAND_RTT */
__attribute__((unused))
static void _ztimer_periph_rtt_start(ztimer_clock_t *clock)
{
(void)clock;
rtt_poweron();
}
__attribute__((unused))
static void _ztimer_periph_rtt_stop(ztimer_clock_t *clock)
{
(void)clock;
rtt_poweroff();
}

Would be an alternative. The advantage would be that the start and stop functions would be compile-tested no matter what. The __attribute__((unused)) is pretty ugly, though.

If you take the suggestion, please do so consistently for all clock backends.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#18884 👀

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've thought about this. It'll add compile time for systems that doesn't make use of ztimer_ondemand_rtt. Do we want that cost? I'm not sure ...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The general trend is to go to e.g. rust where a lot more compile time checks are happening. Also GCC and clang etc became slower and slower over the years, but generate faster and smaller machine code and better diagnostics.

I'd say it is consensus that trading in compile time for faster/smaller/safer code is a good trade off. It certainly is reducing maintenance costs by having larger portions compile time tested.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you okay to address this in a follow-up? There's a lot of code in ztimer applying the same pattern.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! Let's get this in first and address the nit picks afterwards :) I may just open a PR to address this here and elsewhere in ztimer :)

@jue89
Copy link
Contributor Author

jue89 commented Nov 21, 2022

May I squash? (CC: @maribu)

@jue89
Copy link
Contributor Author

jue89 commented Nov 22, 2022

This PR actually brings useful power management to the nrf5x CPU once #18954 and #18953 are merged 🥳

@jue89
Copy link
Contributor Author

jue89 commented Dec 6, 2022

May I gently ping?

Soft freeze of 2023.01 will happen in under 3 weeks - and I'd love to get this one in :-)

Copy link
Member

@maribu maribu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's get this in. Even if people are still nit picking (I also have sime nit picks), let's rather open PRs for that. Then we can have the nitpicks sorted out in parallel and distribute the work. This scales much better.

@jue89
Copy link
Contributor Author

jue89 commented Dec 6, 2022

Some maintainers (@chrysn @maribu @kfessel) had a closer look into this PR. The remaining objection is complaining about RIOT's power management architecture and not this particular implementation. Thus, I'm pressing the shiny green merge button.

This introduces the ztimer_acquire() / ztimer_release() API - although ztimer_ondemand is still opt-in at least until 2023.01 has been released. No one should be bothered by this PR, yet. Cf. my proposed road map.

Thank you very much for your valuable feedback and the support to push this PR forward! :-)

@jue89 jue89 merged commit 71a606a into RIOT-OS:master Dec 6, 2022
@jue89 jue89 deleted the feature/ztimer-ondemand branch December 6, 2022 17:30
@kaspar030 kaspar030 added this to the Release 2023.01 milestone Jan 19, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: doc Area: Documentation Area: Kconfig Area: Kconfig integration Area: sys Area: System Area: tests Area: tests and testing framework Area: timers Area: timer subsystems CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR Reviewed: 1-fundamentals The fundamentals of the PR were reviewed according to the maintainer guidelines
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ztimer and Power Management
6 participants