Skip to content

native: hw_timer: add an absolute deadline for the tick interrupt - #14

Open
npitre wants to merge 1 commit into
BabbleSim:mainfrom
npitre:rfc/abs-tick-deadline
Open

native: hw_timer: add an absolute deadline for the tick interrupt#14
npitre wants to merge 1 commit into
BabbleSim:mainfrom
npitre:rfc/abs-tick-deadline

Conversation

@npitre

@npitre npitre commented Aug 21, 2026

Copy link
Copy Markdown

RFC. Adds a second way to program the tick interrupt: an absolute deadline,
rather than a period plus a count of expiries to skip.

The motivation is fidelity to the hardware being modelled. A free running
counter plus an absolute compare, firing once the counter reaches or passes it,
is what the ARM architected timer (CNTVCT_EL0 / CNTV_CVAL_EL0) and the
RISC-V machine timer (mtime / mtimecmp) are, and the model already hands out
exactly that counter as nsi_hws_get_time(). What it does not expose is the
compare, so a guest driver written for that shape has to convert its deadline
into a count of periods to skip, which rounds it onto the period grid, or
reprogram the period on every timeout so that one period is the delay it wants.

Tested by converting Zephyr's native_sim timer driver to use it, on top of
0d3eafc: hello_world paces correctly (--stop_at=2 in 2 s of wall clock), an
offloaded socket blocking on a peer that replies after 2 real seconds advances
simulated time by 2010 ms, and tests/kernel is 2732/2732 on both native_sim
and native_sim/native/64.

Open questions:

  • Name. hwtimer_wake_in_time() is the existing absolute-time call, so
    hwtimer_tick_in_time() would be symmetric. But the semantics differ
    deliberately: wake_in_time keeps the earlier of two requests, this one
    replaces.
  • Whether "do not use both" should be enforced rather than documented.
    hwtimer_enable() already clears silent_ticks and could clear this too.

The tick interrupt can only be had as a period, via hwtimer_enable(),
with hwtimer_set_silent_ticks() to pass over the expiries not wanted.
Most real timers are not that shape: the ARM architected timer is a free
running counter plus an absolute compare (CNTVCT_EL0, CNTV_CVAL_EL0), and
the RISC-V machine timer is mtime plus mtimecmp. Both fire once the
counter reaches or passes the compare. A driver for either holds an
absolute deadline, and here has to round it onto the period grid or
reprogram the period on every timeout.

hwtimer_set_tick_deadline() raises the same interrupt when simulated time
reaches an absolute time, which nsi_hws_get_time() already hands out. A
deadline already passed fires at once and NSI_NEVER cancels. It uses an
event of its own, so the periodic tick, the awake timer and the real time
pacing are untouched.

Signed-off-by: Nicolas Pitre <npitre@baylibre.com>

@aescolar aescolar left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@npitre would you be so kind as to say what is it that you want to achieve that is not achieved already by using the enable api for this same purpose?
(I did consider adding a one shot api, but the implementation would certainly not have been like this.)

@npitre

npitre commented Aug 23, 2026

Copy link
Copy Markdown
Author

What I want is:

fire_irq_at_or_after(uptime_us): raise an interrupt when the provided time is
reached or already in the past.

Just like many hardware implementations do.

With the enable API a driver holding an absolute deadline has to turn it back
into a delay and re-derive the edge cases: clamp one already passed, avoid a
period of 0, and know that the call also resets silent_ticks and leaves the
period armed for a repeat if nothing re-arms. That belongs in the model rather
than the driver, and a periodic call used as a one-shot is what produced the
pacing regression to begin with.

You say you considered a one shot and would not have implemented it this way:
what shape did you have in mind? I would rather build that than argue for mine.

@aescolar

Copy link
Copy Markdown
Contributor

That can be added.

With the enable API a driver holding an absolute deadline has to turn it back
into a delay

So you don't want the Zephyr driver to use timer_driver_set_reload()?

and a periodic call used as a one-shot is what produced the pacing regression to begin with.

That is a misunderstanding. That issue is already fixed, that other tick timer driver change can now be resubmitted as it was in Zephyr with the native simulator as it is in main.

@npitre

npitre commented Aug 24, 2026

Copy link
Copy Markdown
Author

Fair on the regression, the coupling was the cause and you have fixed it. I
should not have hung that on the API.

On timer_driver_set_reload(): it works. I lean to the compare because the
model's counter is absolute and never wraps, so a deadline is the direct
expression, and the reload path in the core carries catch-up state that exists
for hardware that can only count down. But that is a Zephyr-side choice and not
a reason to hold this up.

What is it you do not like in the implementation here? Happy to respin it, or
leave it to you if you would rather write it yourself.

@aescolar

Copy link
Copy Markdown
Contributor

@npitre if you have use a one shot API like the one proposed in this API, how will the zephyr driver look instead of the using the current hwtimer_enable()?

@npitre

npitre commented Aug 24, 2026

Copy link
Copy Markdown
Author

The bulk of it:

/* Microseconds of simulated time since boot. Never wraps. */
static uint64_t timer_driver_cycle_get(void)
{
	return nsi_hws_get_time();
}

/* The model fires at once for a deadline already passed, hence ORDERED. */
static void timer_driver_set_compare(uint64_t cycles)
{
	hwtimer_set_tick_deadline(cycles);
}

/* Knobs for system_timer_generic.h */
#define TIMER_CORE_BACKEND_COMPARE_ORDERED
#define TIMER_CORE_COUNTER_WIDTH 64

#include "system_timer_generic.h"

static void np_timer_isr(const void *arg)
{
	ARG_UNUSED(arg);

	timer_core_announce();
}

The ordered compare is also the arrangement with the least overhead: the core
hands over the deadline it already computed, with no verify loop, no catch-up
state, and no relative recomputation against a fresh counter read on each arm.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants