Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 37 additions & 5 deletions components/libc/compilers/common/ctime.c
Original file line number Diff line number Diff line change
Expand Up @@ -1356,10 +1356,28 @@ int timer_gettime(timer_t timerid, struct itimerspec *its)
if (timer->status == ACTIVE)
{
unsigned long remain_cnt;
rt_uint64_t total_ns;

rt_clock_hrtimer_control(&timer->hrtimer, RT_TIMER_CTRL_GET_REMAIN_TIME, &remain_cnt);
nanoseconds = ((remain_cnt - rt_clock_time_get_counter()) * rt_clock_time_get_res_scaled()) / RT_CLOCK_TIME_RESMUL;
seconds = nanoseconds / NANOSECOND_PER_SECOND;
nanoseconds = nanoseconds % NANOSECOND_PER_SECOND;

/*
* RT_TIMER_CTRL_GET_REMAIN_TIME returns the absolute expiry count, which
* lives in the low 32 bits of the counter (struct rt_clock_hrtimer keeps
* it as unsigned long). Subtract it from the counter in 32-bit space as
* the hrtimer core does, otherwise the 64-bit counter is subtracted from
* a 32-bit value and the difference underflows.
*/
remain_cnt -= (unsigned long)rt_clock_time_get_counter();
if (remain_cnt > (0xFFFFFFFFUL / 2))
{
/* the deadline has already passed */
remain_cnt = 0;
}

/* a few seconds already exceed UINT32_MAX nanoseconds */
total_ns = ((rt_uint64_t)remain_cnt * rt_clock_time_get_res_scaled()) / RT_CLOCK_TIME_RESMUL;
seconds = (rt_uint32_t)(total_ns / NANOSECOND_PER_SECOND);
nanoseconds = (rt_uint32_t)(total_ns % NANOSECOND_PER_SECOND);
its->it_value.tv_sec = (rt_int32_t)seconds;
its->it_value.tv_nsec = (rt_int32_t)nanoseconds;
}
Expand Down Expand Up @@ -1449,8 +1467,22 @@ int timer_settime(timer_t timerid, int flags, const struct itimerspec *value,
if (ns <= 0)
return 0;

unsigned long res = rt_clock_time_get_res_scaled();
timer->reload = (ns * RT_CLOCK_TIME_RESMUL) / res;
rt_uint64_t res = rt_clock_time_get_res_scaled();

if (res == 0)
{
rt_set_errno(EINVAL);
return -1;
}

/*
* rt_clock_time_get_res_scaled() is a 64-bit value and routinely exceeds
* UINT32_MAX: it is (1e9 * res_scale) / freq, which is 1e13 for a 100 Hz
* source. Keeping it in an unsigned long narrowed it to its low 32 bits and
* armed the timer for a wildly wrong duration (a 10 s timer became 7598005
* ticks, i.e. about 21 hours). Do the conversion in 64 bits.
*/
timer->reload = (unsigned long)(((rt_uint64_t)ns * RT_CLOCK_TIME_RESMUL) / res);
timer->interval.tv_sec = value->it_interval.tv_sec;
timer->interval.tv_nsec = value->it_interval.tv_nsec;
timer->value.tv_sec = value->it_value.tv_sec;
Expand Down
5 changes: 5 additions & 0 deletions components/libc/compilers/common/utest/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,9 @@ config RT_UTEST_LIBC_TIME
default n
depends on RT_USING_POSIX_CLOCK

config RT_UTEST_LIBC_TIMER
bool "timer_gettime() remaining time Test"
default n
depends on RT_USING_POSIX_TIMER

endmenu
3 changes: 3 additions & 0 deletions components/libc/compilers/common/utest/SConscript
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ CPPPATH = [cwd]
if GetDepend(['RT_UTEST_LIBC_TIME']):
src += ['time_tc.c']

if GetDepend(['RT_UTEST_LIBC_TIMER']):
src += ['timer_gettime_tc.c']

group = DefineGroup('utestcases', src, depend = ['RT_USING_UTESTCASES'], CPPPATH = CPPPATH)

Return('group')
97 changes: 97 additions & 0 deletions components/libc/compilers/common/utest/timer_gettime_tc.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright (c) 2006-2026, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*/

/**
* Test Case Name: timer_gettime() remaining time
*
* Test Objectives:
* - Verify that timer_gettime() reports the time the timer was armed with.
* - Verify that durations beyond UINT32_MAX nanoseconds are not truncated.
* - Prevent regression of GitHub Issue #11924.
*
* The tolerances below are one tick wide: the clock source of a BSP is typically
* a periodic tick, so a fraction of a tick may elapse between timer_settime() and
* timer_gettime() and the reported remainder is correspondingly smaller.
*/

#include <rtthread.h>
#include <time.h>
#include <sys/time.h>
#include <signal.h>
#include <string.h>
#include "utest.h"

#define TIMER_GETTIME_TOLERANCE_NS (200ULL * 1000ULL * 1000ULL) /* 200 ms */

/* Compare the reported remainder against the requested duration. */
static void timer_gettime_roundtrip(int seconds, struct sigevent *evp)
{
struct itimerspec value;
struct itimerspec current;
rt_uint64_t requested_ns;
rt_uint64_t reported_ns;
timer_t timer;

LOG_I("checking %d s", seconds);

uassert_int_equal(timer_create(CLOCK_MONOTONIC, evp, &timer), 0);

value.it_value.tv_sec = seconds;
value.it_value.tv_nsec = 0;
value.it_interval.tv_sec = 0;
value.it_interval.tv_nsec = 0;
uassert_int_equal(timer_settime(timer, 0, &value, RT_NULL), 0);

rt_memset(&current, 0, sizeof(current));
uassert_int_equal(timer_gettime(timer, &current), 0);

requested_ns = (rt_uint64_t)seconds * 1000ULL * 1000ULL * 1000ULL;
reported_ns = (rt_uint64_t)current.it_value.tv_sec * 1000ULL * 1000ULL * 1000ULL
+ (rt_uint64_t)current.it_value.tv_nsec;

LOG_I("requested %d s, reported {%ld s, %ld ns}",
seconds, (long)current.it_value.tv_sec, (long)current.it_value.tv_nsec);

/* the remainder may have shrunk by a tick, but it must not exceed the request */
uassert_true(reported_ns <= requested_ns);
uassert_true(reported_ns + TIMER_GETTIME_TOLERANCE_NS >= requested_ns);

/* seconds must not be truncated into a wrong magnitude */
uassert_true((rt_int32_t)current.it_value.tv_sec >= seconds - 1);
uassert_true((rt_int32_t)current.it_value.tv_sec <= seconds);

uassert_int_equal(timer_delete(timer), 0);
}

/* Initialize the timer_gettime test. */
static rt_err_t utest_tc_init(void)
{
return RT_EOK;
}

/* Clean up the timer_gettime test. */
static rt_err_t utest_tc_cleanup(void)
{
return RT_EOK;
}

/* Run the timer_gettime test case. */
static void testcase(void)
{
struct sigevent evp;

rt_memset(&evp, 0, sizeof(evp));
evp.sigev_notify = SIGEV_SIGNAL;
evp.sigev_signo = SIGUSR1;

/* 1 s and 3 s stay below UINT32_MAX ns, 5 s and 10 s do not */
timer_gettime_roundtrip(1, &evp);
timer_gettime_roundtrip(3, &evp);
timer_gettime_roundtrip(5, &evp);
timer_gettime_roundtrip(10, &evp);
}

UTEST_TC_EXPORT(testcase, "components.libc.timer_gettime", utest_tc_init, utest_tc_cleanup, 20);
Loading