Skip to content

drivers/timers: avoid 32-bit overflow in arch_timer current_usec#18848

Merged
jerpelea merged 1 commit intoapache:masterfrom
maxikrie:master
May 6, 2026
Merged

drivers/timers: avoid 32-bit overflow in arch_timer current_usec#18848
jerpelea merged 1 commit intoapache:masterfrom
maxikrie:master

Conversation

@maxikrie
Copy link
Copy Markdown
Contributor

@maxikrie maxikrie commented May 4, 2026

Summary

current_usec() in drivers/timers/arch_timer.c computes elapsed
microseconds as:

return TICK2USEC(timebase) + (status.timeout - status.timeleft);

TICK2USEC(timebase) expands to (timebase) * USEC_PER_TICK. When
timebase is clock_t (uint32_t on 32-bit targets) and
USEC_PER_TICK is 10000, the multiplication overflows uint32_t at
tick 429,497 (~71 minutes at 100 Hz). The result wraps to near-zero.

Because clock_systime_ticks() calls up_timer_gettick()
current_usec() / USEC_PER_TICK when CONFIG_TIMER_ARCH=1, the
scheduler'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 the
future; wd_expiration() never fires it, and the sleeping task
blocks permanently.

Fix: cast timebase to uint64_t before the multiply so the
computation is done in 64-bit arithmetic.

Impact

  • New or modified features: No
  • User-facing changes required: No
  • Build process modifications: No
  • Architecture/board/driver implications: Affects all targets using
    CONFIG_TIMER_ARCH=1 with a 32-bit clock_t
  • Documentation updates: No
  • Security considerations: No
  • Backward/forward compatibility: No

Testing

I confirm that changes are verified on local setup and works as
intended.

Build environment:

  • OS: Linux
  • Target: ARM Cortex-M4 (nRF52840)
  • Compiler: arm-none-eabi-gcc

Target:

  • Architecture: arm
  • Board: nrf52840-dk
  • Config: sdc_nimble_bleprph

Observed before fix: With CONFIG_TIMER_ARCH=1 and a 100 Hz
system tick, usleep() hangs permanently after approximately 71
minutes of uptime. The board remains alive (BLE connectable) but the
sleeping task never wakes. Replacing usleep with a busy-loop never
fails, confirming the watchdog firing path is the issue.

After fix: Board runs the sdc_nimble_bleprph example
continuously past the 71-minute mark without any hang.

PR Verification Self-Check

  • Single functional change
  • All description fields completed
  • Follows NuttX coding standards
  • Not a work in progress
  • Ready for review and merge

@github-actions github-actions Bot added Area: Drivers Drivers issues Size: XS The size of the change in this PR is very small labels May 4, 2026
acassis
acassis previously approved these changes May 4, 2026
jerpelea
jerpelea previously approved these changes May 5, 2026
@linguini1
Copy link
Copy Markdown
Contributor

Could you please format according to the PR template and include some tests?

@maxikrie
Copy link
Copy Markdown
Contributor Author

maxikrie commented May 5, 2026

@acassis I am not sure why the build failed?
@linguini1 Please check again.

@linguini1
Copy link
Copy Markdown
Contributor

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

@maxikrie
Copy link
Copy Markdown
Contributor Author

maxikrie commented May 5, 2026

@linguini1 The root cause is

current_usec() overflowed near 71.6 min
-> up_timer_gettick() returned ticks near 0
-> clock_systime_ticks() jumped backwards
-> usleep watchdog was waiting for an absolute tick around 429500
-> current tick became 3
-> timeout would not expire until time caught up again

Here is a log for when it failed:
sdc/hci systick count=429490(+5) ticks=429490(+5) ctrl=00000007 reload=0009c3ff current=0004ef5b intctrl=00000000
sdc/low systick count=429500(+10) ticks=3(+-429487) ctrl=00000007 reload=0009c3ff current=00067373 intctrl=00000000

Within nrf52_sdc.c I was calling this debug function:
/****************************************************************************

  • Name: nrf52_sdc_debug_systick
    ****************************************************************************/

static void nrf52_sdc_debug_systick(FAR const char *tag)
{
#ifdef CONFIG_ARMV7M_SYSTICK
static uint32_t last_systick_count;
static clock_t last_sched_ticks;
static uint32_t log_count;

uint32_t systick_count = systick_debug_count();
clock_t sched_ticks = clock_systime_ticks();
uint32_t systick_ctrl = getreg32(NVIC_SYSTICK_CTRL);
uint32_t systick_reload = getreg32(NVIC_SYSTICK_RELOAD);
uint32_t systick_current = getreg32(NVIC_SYSTICK_CURRENT);
uint32_t intctrl = getreg32(NVIC_INTCTRL);

log_count++;

printf("sdc/%s systick count=%lu(+%ld) ticks=%lu(+%ld) "
"ctrl=%08lx reload=%08lx current=%08lx intctrl=%08lx\n",
tag,
(unsigned long)systick_count,
(long)(systick_count - last_systick_count),
(unsigned long)sched_ticks,
(long)(sched_ticks - last_sched_ticks),
(unsigned long)systick_ctrl,
(unsigned long)systick_reload,
(unsigned long)systick_current,
(unsigned long)intctrl);

last_systick_count = systick_count;
last_sched_ticks = sched_ticks;

UNUSED(log_count);
#else
UNUSED(tag);
#endif
}

I hope this helps.

linguini1
linguini1 previously approved these changes May 5, 2026
Comment thread drivers/timers/arch_timer.c Outdated
@xiaoxiang781216
Copy link
Copy Markdown
Contributor

#18840 could fix all similar overflow issue.

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>
@jerpelea jerpelea merged commit eb92f0f into apache:master May 6, 2026
41 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: Drivers Drivers issues Size: XS The size of the change in this PR is very small

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants