Skip to content

Big timer API refactoring #10160

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

Closed
wants to merge 13 commits into from

Conversation

andyross
Copy link
Collaborator

@andyross andyross commented Sep 21, 2018

[Note: this PR has been superceded by #10303, which contains all of it and much more]

Big refactoring of the timer subsystem APIs, with a primary goal of
shrinking the surface of the timer driver interface.

Basically: look at sys_clock.h and system_timer.h for the core
meaning: the interfaces are smaller and more orthogonal now,
permitting (much) simpler drivers to be written and allowing for
significant work to the timer backend to clean up the "TICKLESS
#ifdefery" madness. And the driver interface is now documented in
place, so we know what promises are being made and don't have to guess
at the intended meaning of e.g. "_get_elapsed_program_time()".

In particular, note that "tickless" can now be viewed as the default
mode for a driver. If it wants to be simple, it can just deliver one
z_clock_announce() call per tick and ignore the "set_timeout" call as
a hint. Or it can take that hint and arrange to deliver interrupts at
a lower frequency. The kernel doesn't need to care about the
difference!

Tests seem not to have broken with this series, so I'd be comfortable
committing it. But again, this is just refactoring and not a new
feature, so it doesn't have to be rushed.

(And full disclosure: the very fact that nothing seems to have
broken with the tickless changes speaks more to the weakness of our
test coverage in tickless mode more than it does to the code quality
here! I really would have expected to have to chase down at least one
hard bug, and I didn't see anything break.).

@codecov-io
Copy link

codecov-io commented Sep 21, 2018

Codecov Report

Merging #10160 into master will decrease coverage by 0.05%.
The diff coverage is 70.83%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master   #10160      +/-   ##
==========================================
- Coverage   52.58%   52.52%   -0.06%     
==========================================
  Files         213      216       +3     
  Lines       26148    26164      +16     
  Branches     5636     5636              
==========================================
- Hits        13749    13743       -6     
- Misses      10145    10168      +23     
+ Partials     2254     2253       -1
Impacted Files Coverage Δ
kernel/sched.c 91.6% <ø> (ø) ⬆️
include/kernel.h 98.3% <ø> (-0.23%) ⬇️
kernel/thread.c 94.32% <ø> (ø) ⬆️
drivers/timer/sys_clock_init.c 0% <0%> (ø)
kernel/include/timeout_q.h 95.83% <100%> (ø) ⬆️
kernel/idle.c 88.88% <100%> (ø) ⬆️
include/sys_clock.h 100% <100%> (ø)
kernel/mempool.c 88.46% <100%> (ø) ⬆️
drivers/timer/legacy_api.h 100% <100%> (ø)
kernel/sys_clock.c 83.09% <57.14%> (-11.99%) ⬇️
... and 6 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 780c1c2...84ff95f. Read the comment docs.

Andy Ross added 6 commits September 22, 2018 08:06
This header doesn't get included in assembly context, nor does it
provide any asm-usable macros.

Signed-off-by: Andy Ross <[email protected]>
The kernel.h file had a bunch of internal APIs for timeout/clock
handling mixed in.  Move these to sys_clock.h, which it always
included (in a weird location, so move THAT to kernel_includes.h with
everything else).

Signed-off-by: Andy Ross <[email protected]>
The existing API defined sys_clock_{hw_cycles,ticks}_per_sec as simple
"variables" to be shared, except that they were only real storage in
certain modes (the HPET driver, basically) and everywhere else they
were a build constant.

Properly, these should be an API defined by the timer driver (who
controls those rates) and consumed by the clock subsystem.  So give
them function syntax as a stepping stone to get there.

Note that this also removes the deprecated variable
_sys_clock_us_per_tick rather than give it the same treatment.

Signed-off-by: Andy Ross <[email protected]>
This was only used in a few places just to indirect the already
perfectly valid SYS_CLOCK_TICKS_PER_SEC value.  There's no reason for
these to ever have been kconfig units, and in fact the distinction
appears to have introduced a hidden/untested bug in the power
subsystem (the two variables were used interchangably, but they were
defined in reciprocal units!).

Just use "ticks" as our time unit pervasively, and clarify the docs to
explain that.

Signed-off-by: Andy Ross <[email protected]>
This just got turned into a function from a "variable" API, but
post-the-most-recent-patch it turns out to be degenerate anyway.
Everyone everywhere should always have been using the kconfig variable
directly, and it was only a weirdness in the tickless API that made it
confusing.  Fix.

Signed-off-by: Andy Ross <[email protected]>
This was another "global variable" API.  Give it function syntax too.
Also add a warning, because on nRF devices (at least) the cycle clock
runs in kHz and is too slow to give a precise answer here.

Signed-off-by: Andy Ross <[email protected]>
Andy Ross added 7 commits September 22, 2018 08:06
This flag is an indication to the timer driver that the OS doesn't
care about rollover conditions of the tick count while idling, so the
system doesn't need to wake up once per counter flip[1].  Obviously in
that circumstance values returned from k_uptime_get_32() are going to
be wrong, so the implementation had an assert to check for misuse.

But no one understood that from the docs, so the only place these APIs
were used in practice were as "guards" around code that needed to call
k_uptime_get_32(), even though that's 100% wrong per docs!

Clarify the docs.  Remove the incorrect guards.  Change the flag to
initialize to true so that uptime isn't broken-by-default in tickless
mode.  Also move the implemenations of the functions out of the
header, as there's no good reason for these to need to be inlined.

[1] Which can be significant.  A 100MHz ARM using the 24 bit SysTick
    counter rolls over at about 6 Hz, and if it had to come out of
    idle at that rate it would be a significant power issue that would
    swamp the gains from tickless.  Obviously systems with slow
    counters like nRF or 64 bit ones like RISC-V or x86's TSC aren't
    as affected.

Signed-off-by: Andy Ross <[email protected]>
The system tick count is a 64 bit quantity that gets updated from
interrupt context, meaning that it's dangerously non-atomic and has to
be locked.  The core kernel clock code did this right.

But the value was also exposed to the rest of the universe as a global
variable, and virtually nothing else was doing this correctly.  Even
in the timer ISRs themselves, the interrupts may be themselves
preempted (most of our architectures support nested interrupts) by
code that wants to set timeouts and inspect system uptime.

Define a z_tick_{get,set}() API, eliminate the old variable, and make
sure everyone uses the right mechanism.

Signed-off-by: Andy Ross <[email protected]>
This header isn't actually needed in the one assembly context where
it's included.

Signed-off-by: Andy Ross <[email protected]>
There were three separate "announce ticks" entry points exposed for
use by drivers.  Unify them to just a single z_clock_announce()
function, making the "final" tick announcement the business of the
driver only, not the kernel.

Note the oddness with "_sys_idle_elapsed_ticks": this was a global
variable exposed by the kernel.  But it was never actually used by the
kernel.  It was updated and inspected only within the timer drivers,
and only so that it could be passed back to the kernel as the default
(actually hidden) argument to the announce function.  Break this false
dependency by putting this variable into each timer driver
individually.

Signed-off-by: Andy Ross <[email protected]>
The existing API had two almost identical functions: _set_time() and
_timer_idle_enter().  Both simply instruct the timer driver to set the
next timer interrupt expiration appropriately so that the call to
z_clock_announce() will be made at the requested number of ticks.  On
most/all hardware, these should be implementable identically.

Unfortunately because they are specified differently, existing drivers
have implemented them in parallel.

Specify a new, unified, z_clock_set_timeout().  Document it clearly
for implementors.  And provide a shim layer for legacy drivers that
will continue to use the old functions.

Note that this patch fixes an existing bug found by inspection: the
old call to _set_time() out of z_clock_announce() failed to test for
the "wait forever" case in the situation where clock_always_on is
true, meaning that a system that reached this point and then never set
another timeout would freeze its uptime clock incorrectly.

Signed-off-by: Andy Ross <[email protected]>
Rename timer driver API functions to be consistent.  ADD DOCS TO THE
HEADER so implementations understand what the requirements are.
Remove some unused functions that don't need declarations here.

Also removes the per-platform #if's around the power control callback
in favor of a weak-linked noop function in the driver initialization
(adds a few bytes of code to default platforms -- we'll live, I
think).

Signed-off-by: Andy Ross <[email protected]>
The tickless driver had a bunch of "hairy" APIs which forced the timer
drivers to do needless low-level accounting for the benefit of the
kernel, all of which then proceeded to implement them via cut and
paste.  Specifically the "program_time" calls forced the driver to
expose to the kernel exactly when the next interrupt was due and how
much time had elapsed, in a parallel API to the existing "what time is
it" and "announce a tick" interrupts that carry the same information.

Remove these from the kernel, replacing them with synthesized logic
written in terms of the simpler APIs.

In some cases there will be a performance impact due to the use of the
64 bit uptime call, but that will go away soon.

Signed-off-by: Andy Ross <[email protected]>
@@ -15,6 +15,8 @@

static u32_t accumulated_cycle_count;

static s32_t _sys_idle_elapsed_ticks = 1;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could we rename this variable in the drivers?
static _sys_.... located in timer driver does not sound good for me.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The legacy drivers are likely going away and being replaced with much simpler ones written to the new API. I wanted to avoid needless churn there. But sure, to the extent we don't want to rewrite timer drivers doing a cleanup pass after all this mess wouldn't be a bad idea.

@@ -341,3 +341,24 @@ void _nano_sys_clock_tick_announce(s32_t ticks)
}
#endif
}

int k_enable_sys_clock_always_on(void)
Copy link
Collaborator

Choose a reason for hiding this comment

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

IMHO if we are not in tickless mode we we should return:

  • 1 from k_enable_sys_clock_always_on()
  • -ENOTSUP from k_disable_sys_clock_always_on() (requires API change).

Now the API tells that you can always disable this feature, but re-enabling might be not supported.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Agreed, though a little out of scope for this particular pull request. As mentioned in the commit note, this API is sort of broken by design. Personally I'd argue we want to remove it in general and just replace it with a kconfig where the app can tell the kernel whether it wants to allow the clock to lie after system idle or not.

Copy link
Collaborator

Choose a reason for hiding this comment

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

+1 for Kconfig option.

* @return the current system tick count
*
*/
u32_t z_tick_get_32(void);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why we need this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It's used in some tests. More broadly, the tick count is 64 bit and requires locking, so being able to grab just the low word is a performance win. Not a terrible API IMHO.

void _timer_idle_exit(void);
#endif

extern void z_clock_set_timeout(s32_t ticks, bool idle)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why not static inline?

*
* Informs the system clock driver that the next needed call to
* z_clock_announce() will not be until the specified number of ticks
* from the the current time have elapsed. Note that spurious calls
Copy link
Collaborator

Choose a reason for hiding this comment

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

From the current time or from the last tick?
If we use current time as a reference with fast clock + short tick, we might introduce error.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Current time expressed in ticks. I'll clean up these docs before the final series. Remember per disccussion earlier that nothing in the system is allowed to "round" ticks. If you express current time here you're still talking in the same lockstep units.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I am afraid about slipping over tick(s) when new timeout is set. Please consider the nRF51 device: For maximum precision, some would like to have 32768 ticks per second (1 tick = 1 hw cycle). In such setup one tick is equals to ~31 us. The CPU in this SoC runs at 16MHz, so 1 tick is roughly 500 CPU cycles.

The execution path from z_clock_announce() to z_clock_set_timeout() requires some time, and could be preempted by high priority interrupts (for example from BLE stack). So now() (in ticks) at the time of z_clock_set_timeout() might be different than now() announced by z_clock_announce(), which is used by kernel to adjust timeouts list and calculate next event time.

In my opinion we either have to eliminate "now()" from the deltas calculation or make timeout list absolute.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

FWIW: check out the logic in z_clock_announce() that I just pushed in #10303. The current time is adjusted while executing callbacks such that it should be safe against that kind of skew. A timeout added is always atomic relative to the most recently announced tick.

But needless to say, we don't have a test case for that (I guess we could write one with some abusive irq_locking...)

@@ -36,6 +36,8 @@ int z_clock_hw_cycles_per_sec;
*/
static volatile u64_t tick_count;

u64_t z_last_tick_announced;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't we protect access to this variable?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This one is internal to the kernel only, so maybe not. But it's going away anyway -- it exists as a temporary shim while the APIs switch around.

Copy link
Member

@aescolar aescolar left a comment

Choose a reason for hiding this comment

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

I do like the idea, but I reject only to hold merging until I do a proper review (coming soon)

@nashif
Copy link
Member

nashif commented Sep 27, 2018

@aescolar please provide a review ASAP, blocking a PR without a review is not nice and should be avoided. We will not merge this without additional review for sure,, given the weight of the change, so be assured it will get enough reviews.

aescolar
aescolar previously approved these changes Sep 27, 2018
Copy link
Member

@aescolar aescolar left a comment

Choose a reason for hiding this comment

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

Thanks for this, the inline API documentation should make it much easier to implement new drivers.
Just a couple of very minor comments.

extern u32_t _get_elapsed_program_time(void);
#endif

extern void z_clock_set_timeout(s32_t ticks, bool idle)
Copy link
Member

Choose a reason for hiding this comment

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

I guess this extern is a (harmless) typo?

/* Stub implementation of z_clock_set_timeout() in terms of the
* original APIs. Used by older timer drivers. Should be replaced.
*
* Yes, this "header" includes a function definition and must be
Copy link
Member

Choose a reason for hiding this comment

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

Maybe you want to declare the function static to save this note?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It's a public function. Basically the legacy_api.h "header" is a stub of C code that the driver code can include to implement the new API in terms of the old one. It's grown another wart or two in the next pull request too.

And yes: it's ugly. The intent is that this is the shim we use to get the new timer backend working, then we go back and rewrite the timer drivers.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Basically the legacy_api.h "header"

Let me piggyback on this discussion and say that "legacy_api.h" name and stuff like #include "legacy_api.h" looks very confusing to me. Oftentimes, people won't resolve include paths to keep in mind that there's (or maybe not) "legacy_api.h" refers to timer/ directory (or maybe not). Can this be renamed to timer_legacy.h or similar please. (Yes, it will be .../timer/timer_*.h. That's ok.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sorry, missed this one when I closed the bug. FWIW: the double quote inclusion in standard C always refers to the local source directory. Uses like this (i.e. headers that don't define public APIs for inclusion elsewhere) are sort of what it's for. I can look at renaming it though.

But again: the hope is that this header is short-lived. It exists so as to not break Zephyr when used with the old-style timer drivers. Newer ones are much smaller (seriously, like 20-25% the size of the earlier drivers). Take a look at my timerhacks branch for some work getting started on this

* Note that ticks can also be passed the special value K_FOREVER,
* indicating that no future timer interrupts are expected or required
* and that the system is permitted to enter an indefinite sleep even
* if this could cause rolloever of the internal counter (i.e. the
Copy link
Member

Choose a reason for hiding this comment

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

rollo-e-ver

/**
* @brief Timer idle exit notification
*
* This notifies the timer driver that the system is exiting the idle
Copy link
Member

Choose a reason for hiding this comment

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

the idle => from the idle state(?)

@aescolar aescolar dismissed their stale review September 27, 2018 06:16

I did not review it thoroughly enough for a +2, but I did not find anything bad

@andyross
Copy link
Collaborator Author

For those who have been watching this, note that a further evolved series (with real changes, not just refactoring) is up at #10303

@andyross
Copy link
Collaborator Author

The extended PR in #10303 has come together faster than anticipated. I'm going to tag this one DNM but leave it open to finish existing discussions. Please do further review on that one.

@andyross andyross added the DNM This PR should not be merged (Do Not Merge) label Sep 29, 2018
@andyross
Copy link
Collaborator Author

andyross commented Oct 1, 2018

Closing this one. Seems like all conversation has moved to the more current one now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
DNM This PR should not be merged (Do Not Merge)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants