Skip to content

drivers: timer: native_sim_timer: keep the model ticking at the tick period - #116977

Closed
kartben wants to merge 1 commit into
zephyrproject-rtos:mainfrom
kartben:fix-native-sim-timer-real-time
Closed

drivers: timer: native_sim_timer: keep the model ticking at the tick period#116977
kartben wants to merge 1 commit into
zephyrproject-rtos:mainfrom
kartben:fix-native-sim-timer-real-time

Conversation

@kartben

@kartben kartben commented Aug 20, 2026

Copy link
Copy Markdown
Member

Fixes the twister regression on main seen in https://github.com/zephyrproject-rtos/zephyr/actions/runs/32385137294.

Root cause

0342c93 ("drivers: timer: native_sim_timer: use the generic timer core") arms the next announce with hwtimer_enable(cycles). hwtimer_enable() sets the period the timer model free-runs at, so arming stretches the tick period out to the whole span and the model reaches the deadline in a single step.

The model paces simulated time against real time once per tick expiry, so a single step of the whole span leaves nothing to pace it. Under CONFIG_NATIVE_SIM_SLOWDOWN_TO_REAL_TIME (the default whenever !CONFIG_TEST, which covers both failing test directories) an idle with no timeout arms 0x7fffffff ticks, simulated time leaves the wall clock ~248 days behind in one go, and the model then tries to sleep off the difference.

Simulated time on samples/hello_world, native_sim/native/64, after N seconds of wall clock:

0342c930d238^   2s -> Stopped at 2.010s          5s -> Stopped at 5.010s
0342c930d238    2s -> Stopped at 21474836.470s   (0x7fffffff ticks)
this PR         2s -> Stopped at 2.010s          5s -> Stopped at 5.010s

Only tests sharing the simulation with a real-world peer notice, which is why the native offloaded sockets talking to a host openssl s_server / google.com are the ones that broke. A small NSOS round-trip app against a local echo server reports Test PASSED at simulated 00:00:00.180 before the conversion and with this PR, versus 00:17:31.026 on current main.

The fix

Arm through hwtimer_set_silent_ticks(), as the driver did before the conversion: the period stays at one tick, the model keeps stepping -- and pacing -- a tick at a time, and the ticks up to the deadline are passed over without raising an interrupt. hwtimer_enable() goes back to the single call at init that sets that period. The conversion to the generic timer core itself is kept.

Skipping ticks lands on the model's own tick grid rather than exactly cycles from now, so the interrupt can come up to a tick early; the core announces what actually elapsed and the kernel re-arms, so that costs one extra announce and nothing else.

A straight revert of 0342c93 might be a lower-risk alternative.

@kartben
kartben requested a review from npitre August 20, 2026 21:24
@kartben kartben added the Hotfix Fix for issues blocking development, i.e. upstream CI issues, tests failing in upstream CI , etc. label Aug 20, 2026
@kartben
kartben marked this pull request as ready for review August 20, 2026 21:26
@zephyrbot zephyrbot added area: native port Host native arch port (native_sim) area: Timer Timer labels Aug 20, 2026
…period

hwtimer_enable() sets the period the timer model free-runs at, so arming
the next announce with it stretched that period out to the whole span and
let the model reach the deadline in a single step.

The model paces simulated time against real time once per tick expiry, so
one step of the whole span leaves nothing to pace it. Under
CONFIG_NATIVE_SIM_SLOWDOWN_TO_REAL_TIME an idle with no timeout arms
0x7fffffff ticks, simulated time leaves the wall clock ~248 days behind in
one go, and the model then tries to sleep off the difference. Simulated
time is no longer related to real time and anything sharing the simulation
with a real-world peer stalls: CI hit this on the native offloaded sockets
tests that talk to a host TLS server (net.sockets.tls12.ec_kex,
net.sockets.tls12.psk_kex and sample.net.sockets.http_get.nsos.https all
hung in connect()).

Arm through hwtimer_set_silent_ticks() instead, as the driver did before
the conversion: the period stays at one tick, the model keeps stepping --
and pacing -- a tick at a time, and the ticks up to the deadline are passed
over without raising an interrupt. hwtimer_enable() goes back to the single
call at init that sets that period.

Signed-off-by: Benjamin Cabé <benjamin@zephyrproject.org>
Assisted-by: Claude:opus-5

@npitre npitre 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.

My regression, thanks for chasing it down.

Two things to fold in.

cycles / NP_TICK_PERIOD_US wants to be DIV_ROUND_UP(cycles, NP_TICK_PERIOD_US). When an arm lands off the model's tick grid the floor drops
a whole tick, so the interrupt comes a tick early and the kernel re-arms: two
interrupts per timeout instead of one. Measured on native_sim/native/64, 40
iterations of k_busy_wait(3333) plus k_msleep(50), counting entries to
np_timer_isr():

ISRs per sleep elapsed
before 0342c93 1.00 2400 ms
this PR 2.00 2400 ms
this PR + DIV_ROUND_UP 1.00 2400 ms

Same elapsed either way, so rounding up does not fire late. A plain k_msleep()
loop shows 1.00 in all three, since with the CPU consuming no simulated time
every arm lands on the grid; it takes another event to knock them off, which the
offloaded-socket tests have no shortage of.

Separately, the BUILD_ASSERT does not compile with
CONFIG_SYSTEM_CLOCK_HW_CYCLES_PER_SEC_RUNTIME_UPDATE=y:

drivers/timer/native_sim_timer.c:62:32: error: expression in static assertion is not constant

Same class as #116777. Nothing in tree sets it on native_sim, but skipping the
assert would not be enough: if it were set, NP_TICK_PERIOD_US would diverge
from the runtime rate and the silent-tick arithmetic would be quietly wrong. A
simulated microsecond cannot be re-rated, so I would #error on the option.

@aescolar aescolar removed the Hotfix Fix for issues blocking development, i.e. upstream CI issues, tests failing in upstream CI , etc. label Aug 21, 2026

@aescolar aescolar left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's not do it this way.

@aescolar

Copy link
Copy Markdown
Member

Thanks for attempting a fix.
The diagnostic is mostly correct, but this fix is coupling things back that shouldn't be.
I should have properly realized about this issue in the original PR review. (I did, but in a split second I discarded it thinking that all Zephyr drivers for native_sim pace themselves using the kernel, but I forgot about the nsos driver, and also about possible out of tree ones).
The timer driver paces the real time sync based on the kernel tick since the original native_posix prototype, just because I cut corners (the kernel ticked with a period that matched quite well this need). But I should not have mixed them; They are 2 separate issues.
I will just change it now.

@kartben

kartben commented Aug 21, 2026

Copy link
Copy Markdown
Member Author

Closing as this has no reason to exist anymore now that original commit has been reverted. Thanks both @npitre and @aescolar for the follow-up!

@kartben kartben closed this Aug 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: native port Host native arch port (native_sim) area: Timer Timer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants