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
Merged
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
110 changes: 100 additions & 10 deletions sys/include/ztimer.h
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,20 @@ typedef struct {
* @param clock ztimer clock to cancel a pending alarm, if any
*/
void (*cancel)(ztimer_clock_t *clock);

#if MODULE_ZTIMER_ONDEMAND || DOXYGEN
Copy link
Member

@maribu maribu Feb 15, 2022

Choose a reason for hiding this comment

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

Let's do some bike shedding: Why not ztimer_pm instead of ztimer_ondemand?

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 think ondemand is a little more verbose what this pseudo module actually does. Boards making use of the driver would pull in ztimer_ondemand_timer if periph_timer shall be turned on and off. The interaction with power management will only happen indirectly through periph_timer.

But of course this is only my point of view. I'm completely open to other suggestions! I don't have any strong feelings regarding the name ;-)

/**
* @brief Starts the underlying timer
* @param clock ztimer clock to start
*/
void (*start)(ztimer_clock_t *clock);

/**
* @brief Stops the underlying timer
Copy link
Contributor

Choose a reason for hiding this comment

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

this should not force the timer to be stopped but free the timer to be stopable when powermanagement requires that (that should be reflected in the name) (stoping and starting timer takes time sometimes we might not even know the amount (the compensation value might only be good for a average case) (e.g. if stating and stopping requires a crystal to get running or some mcu that have register sync cycles or need clock phases to align)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, but in that case nrf52 won't gain any power savings. Peripherals have to disabled explicitly. There's no special power management unit for nrf52, IIRC. Aside from that, periph_timer, ... don't have an API to indicate that the timer is allowed to stall silently.

Copy link
Contributor

Choose a reason for hiding this comment

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

i just looked at it (seems like the nrf52 has its own PM task running in hardware), but there are other that do not, for nrf52 it might make sense to immediately stop, but for other mcu it might just free up a pmlevel. mcu not have these things if you stop their timer (for pm) they might lose the status of that timer therefor the start has to reinitialize the timer

some cant be stopped (pipico) (but if you do not need the timer you may not keep the osc running when it going to sleep)

Copy link
Contributor

Choose a reason for hiding this comment

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

btw there is a possible race condition in the nrf52 hardware os that might throw of your timing if you immediately stop at 0 users (the manual warns about starting and stopping in the same PCLK16M (stop will win) ) so stopping should wait until you are quiet sure it will no be started soon.
for the sam you need to do what the nrf52 does for you in software ( disable unneed clocks) you might stop the timer but that will not safe much power.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

mcu not have these things if you stop their timer (for pm) they might lose the status of that timer therefor the start has to reinitialize the timer

I expect timer_stop() not to completely deinit the peripheral. And I haven't observed this happening on any cpu I worked with.

some cant be stopped (pipico) (but if you do not need the timer you may not keep the osc running when it going to sleep)

I this case the timer_stop() and timer_start() just interact with pm_layered and block resp. unblock the right pm state.

--

What is your suggestion how to improve this timer system? I'm still convinced that the direct interaction with pm_layered is a bad idea. I've collected many lost hours because of silly bugs caused by this implementation. Its pure pain in the butt.

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 did the same experiment on #18821 with the samr30-xpro board:

  • timer_start(): 2.168us
  • timer_stop(): 2.664us

-> the introduced pm_block()/pm_unblock() adds a delay of ~1.2us each! Most of the time is lost in pm_layered. And we can't skip interacting with it.

--

And the same experiment on #18780 (with #18814 applied):

  • timer_start(): 2.500us
  • timer_stop(): 2.580us

This also includes the interaction with pm_layered.

Copy link
Contributor

Choose a reason for hiding this comment

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

but you need to do the pm part of the pm ( which seems to be ~1us) while u do not need to to the starting and stopping of timer another 1 to 1.5 us (if you just stop you don't do pm) pm and timer stoping are different things on most mcus. Do both on your example mcu -> 2 us, do one without the other 1 us. -> have different functions for that

Copy link
Member

Choose a reason for hiding this comment

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

Does this need to be sorted out necessarily for this PR to go in (vs. adjusting the implementation in a follow-up)? I don't understand all the discussion's details, but it appears to me that it's about performance of a weird use case (a loop repeatedly setting a timer would be better off setting a periodic timer and clearing it on a break), whereas this PR as a whole s necessary to make ztimer usable correctly.

Copy link
Contributor Author

@jue89 jue89 Nov 1, 2022

Choose a reason for hiding this comment

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

Well, just having the empty ztimer_acquire() / ztimer_release() interface doesn't gain any power savings ;-) And we need that! RIOT is doing a terrible job on that right now ... we as a company are running forks to fix that for our use cases - it scales badly. We are still on RIOT 2020.04 in production because of that.

And introducing such interfaces without any implementation behind can result in a situation where future implementations don't work with that introduced interface. I really don't want that either.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

And the results of the test program executed on an weact-f411ce board on current master:

  • timer_stop(): 0.264us
  • timer_start(): 0.208us

I'm afraid, I don't have further boards at hand.

* @param clock ztimer clock to stop
*/
void (*stop)(ztimer_clock_t *clock);
#endif
} ztimer_ops_t;

/**
Expand All @@ -373,14 +387,21 @@ struct ztimer_clock {
uint16_t adjust_set; /**< will be subtracted on every set() */
uint16_t adjust_sleep; /**< will be subtracted on every sleep(),
in addition to adjust_set */
#if MODULE_ZTIMER_ONDEMAND || DOXYGEN
uint16_t adjust_clock_start; /**< will be subtracted on every set(),
if the underlying periph is in
stopped state */
uint16_t users; /**< user count of this clock */
#endif
#if MODULE_ZTIMER_EXTEND || MODULE_ZTIMER_NOW64 || DOXYGEN
/* values used for checkpointed intervals and 32bit extension */
uint32_t max_value; /**< maximum relative timer value */
uint32_t lower_last; /**< timer value at last now() call */
ztimer_now_t checkpoint; /**< cumulated time at last now() call */
#endif
#if MODULE_PM_LAYERED || DOXYGEN
uint8_t block_pm_mode; /**< min. pm mode to block for the clock to run */
#if MODULE_PM_LAYERED && !MODULE_ZTIMER_ONDEMAND || DOXYGEN
uint8_t block_pm_mode; /**< min. pm mode to block for the clock to run
don't use in combination with ztimer_ondemand! */
#endif
};

Expand All @@ -393,6 +414,47 @@ struct ztimer_clock {
void ztimer_handler(ztimer_clock_t *clock);

/* User API */
/**
* @brief Acquire a clock
*
* This will indicate the the underlying clock is required to be running.
* If time differences are measured using @ref ztimer_now this will make
* sure ztimer won't turn of the clock source.
*
* @param[in] clock ztimer clock to operate on
*
* @return true if this was the first acquisition on this this clock
*/
#if MODULE_ZTIMER_ONDEMAND || DOXYGEN
bool ztimer_acquire(ztimer_clock_t *clock);
#else
static inline bool ztimer_acquire(ztimer_clock_t *clock)
{
(void) clock;
return false;
}
#endif

/**
* @brief Release a clock
*
* This will indicate the the underlying clock isn't required to be running
* anymore and may be turned off.
*
* @param[in] clock ztimer clock to operate on
*
* @return true if this was the last clock user
*/
#if MODULE_ZTIMER_ONDEMAND || DOXYGEN
bool ztimer_release(ztimer_clock_t *clock);
#else
static inline bool ztimer_release(ztimer_clock_t *clock)
{
(void) clock;
return false;
}
#endif

/**
* @brief Set a timer on a clock
*
Expand Down Expand Up @@ -492,6 +554,17 @@ int ztimer_msg_receive_timeout(ztimer_clock_t *clock, msg_t *msg,
*/
ztimer_now_t _ztimer_now_extend(ztimer_clock_t *clock);

/**
* @brief asserts the given clock to be active
*
* @internal
*
* @note This function is internal
*
* @param[in] clock ztimer clock to operate on
*/
void _ztimer_assert_clock_active(ztimer_clock_t *clock);

/**
* @brief Get the current time from a clock
*
Expand All @@ -511,14 +584,18 @@ ztimer_now_t _ztimer_now_extend(ztimer_clock_t *clock);
* * Two values can only be compared when the clock has been continuously
* active between the first and the second reading.
*
* A clock is guaranteed to be active from the time any timer is set (the
* first opportunity to get a "now" value from it is the return value of @ref
* ztimer_set) until the time the timer's callback returns. The clock also
* stays active when timers are set back-to-back (which is the case when the
* first timer's callback sets the second timer), or when they overlap (which
* can be known by starting the second timer and afterwards observing that
* @ref ztimer_is_set or @ref ztimer_remove returns true in a low-priority
* context).
* Calling @ref ztimer_acquire before using `ztimer_now()` is the preferred
* way to guarantee that a clock is continuously active. Make sure to call
* the corresponding @ref ztimer_release after the last `ztimer_now()` call.
*
* A clock is also guaranteed to be active from the time any timer is set
* (the first opportunity to get a "now" value from it is the return value of
* @ref ztimer_set) until the time the timer's callback returns. The clock
* also stays active when timers are set back-to-back (which is the case when
* the first timer's callback sets the second timer), or when they overlap
* (which can be known by starting the second timer and afterwards observing
* that @ref ztimer_is_set or @ref ztimer_remove returns true in a
* low-priority context).
*
* In contrast, the clock is not guaranteed to be active if a timer is
* removed and then a second one is started (even if the thread does not
Expand Down Expand Up @@ -593,12 +670,19 @@ ztimer_now_t _ztimer_now_extend(ztimer_clock_t *clock);
* unrelated components are altered that change the systems's power
* management behavior.
*
* @warning Make sure to call @ref ztimer_acquire(@p clock) before fetching
* the clock's current time.
*
* @param[in] clock ztimer clock to operate on
*
* @return Current count on @p clock
*/
static inline ztimer_now_t ztimer_now(ztimer_clock_t *clock)
{
#if MODULE_ZTIMER_ONDEMAND && DEVELHELP
_ztimer_assert_clock_active(clock);
#endif

#if MODULE_ZTIMER_NOW64
if (1) {
#elif MODULE_ZTIMER_EXTEND
Expand Down Expand Up @@ -627,6 +711,10 @@ static inline ztimer_now_t ztimer_now(ztimer_clock_t *clock)
* If the time (@p last_wakeup + @p period) has already passed, the function
* sets @p last_wakeup to @p last_wakeup + @p period and returns immediately.
*
* @warning Make sure to call @ref ztimer_acquire(@p clock) before making use
* of @ref ztimer_periodic_wakeup. After usage
* @ref ztimer_release(@p clock) should be called.
*
* @param[in] clock ztimer clock to operate on
* @param[in] last_wakeup base time stamp for the wakeup
* @param[in] period time in ticks that will be added to @p last_wakeup
Expand All @@ -652,11 +740,13 @@ void ztimer_sleep(ztimer_clock_t *clock, uint32_t duration);
*/
static inline void ztimer_spin(ztimer_clock_t *clock, uint32_t duration)
{
ztimer_acquire(clock);
uint32_t end = ztimer_now(clock) + duration;

/* Rely on integer overflow. `end - now` will be smaller than `duration`,
* counting down, until it underflows to UINT32_MAX. Loop ends then. */
while ((end - ztimer_now(clock)) <= duration) {}
ztimer_release(clock);
}

/**
Expand Down
13 changes: 13 additions & 0 deletions sys/include/ztimer/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,19 @@ extern "C" {
# endif
#endif

/**
* @brief An offset for ZTIMER_USEC allowing to compensate for the offset
* introduced by turning on the underlying peripheral.
*
* @note This value can be measured with the
* `tests/ztimer_ondemand_benchmark` tool.
*
* This value should be configured in the board.h.
*/
#ifndef CONFIG_ZTIMER_USEC_ADJUST_CLOCK_START
#define CONFIG_ZTIMER_USEC_ADJUST_CLOCK_START 0
#endif

/**
* @brief An offset for ZTIMER_USEC allowing to compensate for the offset
* of @ref ztimer_set(). It can be measured with @ref ztimer_overhead_set()
Expand Down
18 changes: 18 additions & 0 deletions sys/include/ztimer/convert.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,24 @@ void ztimer_convert_init(ztimer_convert_t *ztimer_convert,
*/
void ztimer_convert_cancel(ztimer_clock_t *clock);

/**
* @brief ztimer_convert common start() op
*
* Used by some conversion modules as ztimer_clock_t::ops.start().
*
* @param[in] clock ztimer clock to operate on
*/
void ztimer_convert_start(ztimer_clock_t *clock);

/**
* @brief ztimer_convert common stop() op
*
* Used by some conversion modules as ztimer_clock_t::ops.stop().
*
* @param[in] clock ztimer clock to operate on
*/
void ztimer_convert_stop(ztimer_clock_t *clock);

#ifdef __cplusplus
}
#endif
Expand Down
3 changes: 3 additions & 0 deletions sys/include/ztimer/mock.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,14 @@ typedef struct {
uint32_t now; /**< current counter value */
uint32_t target; /**< ticks left until alarm is hit */
unsigned armed; /**< flag for checking if a target has been set */
unsigned running; /**< flag for checking if the timer is running */

struct {
unsigned now; /**< Number of calls to ztimer_ops_t::now */
unsigned set; /**< Number of calls to ztimer_ops_t::set */
unsigned cancel; /**< Number of calls to ztimer_ops_t::cancel */
unsigned start; /**< Number of calls to ztimer_ops_t::start */
unsigned stop; /**< Number of calls to ztimer_ops_t::stop */
} calls; /**< Struct holding number of calls to each
operation */
} ztimer_mock_t;
Expand Down
36 changes: 36 additions & 0 deletions sys/ztimer/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,42 @@ config MODULE_ZTIMER_MOCK
manually fired to simulate different scenarios and test the ztimer
implementation using this as a backing timer.

menuconfig MODULE_ZTIMER_ONDEMAND
bool "Run ztimer clocks only on demand"
help
This ztimer extensions keeps track of ztimer users and may disable
underlying peripherals if not users are requiring a ztimer clock.

if MODULE_ZTIMER_ONDEMAND

config MODULE_ZTIMER_ONDEMAND_STRICT
bool "Strict ztimer on demand"
help
Enforce ztimer clocks to be running before calling ztimer_now().

config MODULE_ZTIMER_ONDEMAND_TIMER
bool "Run Timer only on demand"
depends on MODULE_ZTIMER_PERIPH_TIMER
help
Turn off the underlying Timer peripheral if related ztimer clocks
have no users.

config MODULE_ZTIMER_ONDEMAND_RTT
bool "Run RTT only on demand"
depends on MODULE_ZTIMER_PERIPH_RTT
help
Turn off the underlying RTT peripheral if related ztimer clocks
have no users.

config MODULE_ZTIMER_ONDEMAND_RTC
bool "Run RTC only on demand"
depends on MODULE_ZTIMER_PERIPH_RTC
help
Turn off the underlying RTC peripheral if related ztimer clocks
have no users.

endif # MODULE_ZTIMER_ONDEMAND

config MODULE_ZTIMER_INIT
bool

Expand Down
4 changes: 4 additions & 0 deletions sys/ztimer/Makefile.dep
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ ifneq (,$(filter ztimer_%,$(USEMODULE)))
USEMODULE += ztimer_extend
endif

ifneq (,$(filter ztimer_ondemand_%,$(USEMODULE)))
USEMODULE += ztimer_ondemand
endif

ifneq (,$(filter ztimer_convert_%,$(USEMODULE)))
USEMODULE += ztimer_convert
endif
Expand Down
16 changes: 15 additions & 1 deletion sys/ztimer/convert.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,20 @@ void ztimer_convert_cancel(ztimer_clock_t *clock)
ztimer_remove(ztimer_convert->lower, &ztimer_convert->lower_entry);
}

void ztimer_convert_start(ztimer_clock_t *clock)
{
ztimer_convert_t *ztimer_convert = (ztimer_convert_t *)clock;

ztimer_acquire(ztimer_convert->lower);
}

void ztimer_convert_stop(ztimer_clock_t *clock)
{
ztimer_convert_t *ztimer_convert = (ztimer_convert_t *)clock;

ztimer_release(ztimer_convert->lower);
}

void ztimer_convert_init(ztimer_convert_t *ztimer_convert,
ztimer_clock_t *lower,
uint32_t max_value)
Expand All @@ -46,7 +60,7 @@ void ztimer_convert_init(ztimer_convert_t *ztimer_convert,
.arg = ztimer_convert,
},
.super.max_value = max_value,
# ifdef MODULE_PM_LAYERED
# if MODULE_PM_LAYERED && !MODULE_ZTIMER_ONDEMAND
.super.block_pm_mode = ZTIMER_CLOCK_NO_REQUIRED_PM_MODE,
# endif
};
Expand Down
11 changes: 10 additions & 1 deletion sys/ztimer/convert_frac.c
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ static const ztimer_ops_t ztimer_convert_frac_ops = {
.set = ztimer_convert_frac_op_set,
.now = ztimer_convert_frac_op_now,
.cancel = ztimer_convert_cancel,
#if MODULE_ZTIMER_ONDEMAND
.start = ztimer_convert_start,
.stop = ztimer_convert_stop,
#endif
};

static void ztimer_convert_frac_compute_scale(ztimer_convert_frac_t *self,
Expand Down Expand Up @@ -103,15 +107,20 @@ void ztimer_convert_frac_init(ztimer_convert_frac_t *self,
ztimer_convert_frac_compute_scale(self, freq_self, freq_lower);
if (freq_self < freq_lower) {
self->super.super.max_value = frac_scale(&self->scale_now, UINT32_MAX);
#if !MODULE_ZTIMER_ONDEMAND
/* extend lower clock only if the ondemand driver isn't selected
* otherwise, the clock extension will be called with the first
* ztimer_acquire() call */
ztimer_init_extend(&self->super.super);
#endif
}
else {
DEBUG("ztimer_convert_frac_init: rounding up val:%" PRIu32 "\n",
(uint32_t)(freq_self / freq_lower));
self->round = freq_self / freq_lower;
self->super.super.max_value = UINT32_MAX;
}
#ifdef MODULE_PM_LAYERED
#if MODULE_PM_LAYERED && !MODULE_ZTIMER_ONDEMAND
self->super.super.block_pm_mode = ZTIMER_CLOCK_NO_REQUIRED_PM_MODE;
#endif
}
9 changes: 9 additions & 0 deletions sys/ztimer/convert_muldiv64.c
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ static const ztimer_ops_t _ztimer_convert_muldiv64_ops = {
.set = _ztimer_convert_muldiv64_set,
.now = _ztimer_convert_muldiv64_now,
.cancel = ztimer_convert_cancel,
#if MODULE_ZTIMER_ONDEMAND
.start = ztimer_convert_start,
.stop = ztimer_convert_stop,
#endif
};

void ztimer_convert_muldiv64_init(
Expand All @@ -121,5 +125,10 @@ void ztimer_convert_muldiv64_init(
ztimer_convert_muldiv64->super.super.ops = &_ztimer_convert_muldiv64_ops;
ztimer_convert_muldiv64->div = div;
ztimer_convert_muldiv64->mul = mul;
#if !MODULE_ZTIMER_ONDEMAND
/* extend lower clock only if the ondemand driver isn't selected
* otherwise, the clock extension will be called with the first
* ztimer_acquire() call */
ztimer_init_extend(&ztimer_convert_muldiv64->super.super);
#endif
}
4 changes: 4 additions & 0 deletions sys/ztimer/convert_shift.c
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ static const ztimer_ops_t _ztimer_convert_shift_ops_up = {
.set = _ztimer_convert_shift_up_set,
.now = _ztimer_convert_shift_up_now,
.cancel = ztimer_convert_cancel,
#if MODULE_ZTIMER_ONDEMAND
.start = ztimer_convert_start,
.stop = ztimer_convert_stop,
#endif
};

void ztimer_convert_shift_up_init(ztimer_convert_shift_t *clock,
Expand Down
Loading