drivers/timers: avoid 32-bit overflow in arch_timer current_usec#18848
drivers/timers: avoid 32-bit overflow in arch_timer current_usec#18848maxikrie wants to merge 1 commit intoapache:masterfrom
Conversation
|
Could you please format according to the PR template and include some tests? |
|
@acassis I am not sure why the build failed? |
|
Could you please include the logs from your testing? I think the building is failing due to an earlier issue. Please rebase onto master, and that should fix it |
|
@linguini1 The root cause is current_usec() overflowed near 71.6 min Here is a log for when it failed: Within nrf52_sdc.c I was calling this debug function:
static void nrf52_sdc_debug_systick(FAR const char *tag) uint32_t systick_count = systick_debug_count(); log_count++; printf("sdc/%s systick count=%lu(+%ld) ticks=%lu(+%ld) " last_systick_count = systick_count; UNUSED(log_count); I hope this helps. |
current_usec() returns a uint64_t, but it used TICK2USEC(timebase) to convert scheduler ticks to microseconds. On 32-bit clock_t builds, TICK2USEC() performs the multiplication in 32-bit arithmetic before the result is widened. With CONFIG_USEC_PER_TICK=10000, this wraps after about 71.6 minutes: UINT32_MAX / 1000000 ~= 4294 seconds After the wrap, up_timer_gettick() can report time near zero again. This can leave absolute watchdog timeouts, such as those used by usleep() / clock_nanosleep(), waiting for a tick value that will not be reached until the 32-bit scheduler counter wraps. Cast timebase to uint64_t before multiplying by USEC_PER_TICK so current_usec() remains monotonic across the 32-bit microsecond boundary. Signed-off-by: Max Kriegleder <max.kriegleder@gmail.com>
Summary
current_usec()indrivers/timers/arch_timer.ccomputes elapsedmicroseconds as:
TICK2USEC(timebase)expands to(timebase) * USEC_PER_TICK. Whentimebaseisclock_t(uint32_t on 32-bit targets) andUSEC_PER_TICKis 10000, the multiplication overflows uint32_t attick 429,497 (~71 minutes at 100 Hz). The result wraps to near-zero.
Because
clock_systime_ticks()callsup_timer_gettick()→current_usec() / USEC_PER_TICKwhenCONFIG_TIMER_ARCH=1, thescheduler's tick counter suddenly reads ~0 instead of ~429,497. Any
watchdog armed just before the overflow (e.g. for
usleep/nanosleep) has an expiry tick that now appears to be far in thefuture;
wd_expiration()never fires it, and the sleeping taskblocks permanently.
Fix: cast
timebasetouint64_tbefore the multiply so thecomputation is done in 64-bit arithmetic.
Impact
CONFIG_TIMER_ARCH=1with a 32-bitclock_tTesting
I confirm that changes are verified on local setup and works as
intended.
Build environment:
Target:
Observed before fix: With
CONFIG_TIMER_ARCH=1and a 100 Hzsystem tick,
usleep()hangs permanently after approximately 71minutes of uptime. The board remains alive (BLE connectable) but the
sleeping task never wakes. Replacing
usleepwith a busy-loop neverfails, confirming the watchdog firing path is the issue.
After fix: Board runs the
sdc_nimble_bleprphexamplecontinuously past the 71-minute mark without any hang.
PR Verification Self-Check