mirror of
https://github.com/apache/nuttx.git
synced 2026-08-24 15:08:47 +00:00
Implement Linux-compatible TFD_TIMER_CANCEL_ON_SET flag for timerfd to allow applications to detect discontinuous changes to CLOCK_REALTIME. Background: According to Linux timerfd_create(2) man page, when a timerfd is created with CLOCK_REALTIME and TFD_TIMER_CANCEL_ON_SET flag is specified, the read() operation should fail with ECANCELED if the real-time clock undergoes a discontinuous change. This allows applications to detect and respond to system time changes. Implementation: 1. Add clock notifier infrastructure (clock_notifier.h/c) to notify interested parties when system time changes 2. Implement TFD_TIMER_CANCEL_ON_SET flag support in timerfd 3. Register timerfd with clock notifier when flag is set 4. Cancel timer and return ECANCELED when clock change is detected 5. Notify clock changes in: - clock_settime() - clock_initialize() - clock_timekeeping_set_wall_time() - rpmsg_rtc time synchronization Changes include: - New files: include/nuttx/clock_notifier.h, sched/clock/clock_notifier.c - Modified: fs/vfs/fs_timerfd.c to handle TFD_TIMER_CANCEL_ON_SET - Modified: clock subsystem to call notifier chain on time changes - Modified: rpmsg_rtc to notify time changes during sync Use case example: Applications using timerfd with absolute time can now detect when system time is adjusted (e.g., NTP sync, manual time change) and take appropriate action such as recalculating timeouts or updating scheduled events. Reference: https://man7.org/linux/man-pages/man2/timerfd_create.2.html Signed-off-by: dongjiuzhu1 <dongjiuzhu1@xiaomi.com>
305 lines
8.5 KiB
C
305 lines
8.5 KiB
C
/****************************************************************************
|
|
* sched/clock/clock_timekeeping.c
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership. The
|
|
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance with the
|
|
* License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
#ifdef CONFIG_CLOCK_TIMEKEEPING
|
|
|
|
#include <sys/time.h>
|
|
#include <stdint.h>
|
|
#include <time.h>
|
|
#include <errno.h>
|
|
#include <debug.h>
|
|
|
|
#include <nuttx/irq.h>
|
|
#include <nuttx/arch.h>
|
|
#include <nuttx/clock_notifier.h>
|
|
|
|
#include "clock/clock.h"
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
#define NTP_MAX_ADJUST 500
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
static struct timespec g_clock_wall_time;
|
|
static uint64_t g_clock_last_counter;
|
|
static uint64_t g_clock_mask;
|
|
static long g_clock_adjust;
|
|
static spinlock_t g_clock_lock = SP_UNLOCKED;
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: clock_get_current_time
|
|
****************************************************************************/
|
|
|
|
static int clock_get_current_time(FAR struct timespec *ts,
|
|
FAR struct timespec *base)
|
|
{
|
|
irqstate_t flags;
|
|
uint64_t counter;
|
|
uint64_t offset;
|
|
uint64_t nsec;
|
|
time_t sec;
|
|
int ret;
|
|
|
|
flags = spin_lock_irqsave(&g_clock_lock);
|
|
|
|
ret = up_timer_gettick(&counter);
|
|
if (ret < 0)
|
|
{
|
|
goto errout_in_critical_section;
|
|
}
|
|
|
|
offset = (counter - g_clock_last_counter) & g_clock_mask;
|
|
nsec = offset * NSEC_PER_TICK;
|
|
sec = nsec / NSEC_PER_SEC;
|
|
nsec -= sec * NSEC_PER_SEC;
|
|
|
|
nsec += base->tv_nsec;
|
|
if (nsec >= NSEC_PER_SEC)
|
|
{
|
|
nsec -= NSEC_PER_SEC;
|
|
sec += 1;
|
|
}
|
|
|
|
ts->tv_nsec = nsec;
|
|
ts->tv_sec = base->tv_sec + sec;
|
|
|
|
errout_in_critical_section:
|
|
spin_unlock_irqrestore(&g_clock_lock, flags);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: clock_timekeeping_get_wall_time
|
|
****************************************************************************/
|
|
|
|
int clock_timekeeping_get_wall_time(FAR struct timespec *ts)
|
|
{
|
|
return clock_get_current_time(ts, &g_clock_wall_time);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: clock_timekeeping_set_wall_time
|
|
****************************************************************************/
|
|
|
|
int clock_timekeeping_set_wall_time(FAR const struct timespec *ts)
|
|
{
|
|
irqstate_t flags;
|
|
uint64_t counter;
|
|
int ret;
|
|
|
|
flags = spin_lock_irqsave(&g_clock_lock);
|
|
|
|
ret = up_timer_gettick(&counter);
|
|
if (ret < 0)
|
|
{
|
|
goto errout_in_critical_section;
|
|
}
|
|
|
|
memcpy(&g_clock_wall_time, ts, sizeof(struct timespec));
|
|
clock_notifier_call_chain(CLOCK_REALTIME, ts);
|
|
|
|
g_clock_adjust = 0;
|
|
g_clock_last_counter = counter;
|
|
|
|
errout_in_critical_section:
|
|
spin_unlock_irqrestore(&g_clock_lock, flags);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: adjtime
|
|
*
|
|
* Description:
|
|
* The adjtime() function gradually adjusts the system clock (as returned
|
|
* by gettimeofday(2)). The amount of time by which the clock is to be
|
|
* adjusted is specified in the structure pointed to by delta.
|
|
*
|
|
* This structure has the following form:
|
|
*
|
|
* struct timeval
|
|
* {
|
|
* time_t tv_sec; (seconds)
|
|
* suseconds_t tv_usec; (microseconds)
|
|
* };
|
|
*
|
|
* If the adjustment in delta is positive, then the system clock is
|
|
* speeded up by some small percentage (i.e., by adding a small amount of
|
|
* time to the clock value in each second) until the adjustment has been
|
|
* completed. If the adjustment in delta is negative, then the clock is
|
|
* slowed down in a similar fashion.
|
|
*
|
|
* If a clock adjustment from an earlier adjtime() call is already in
|
|
* progress at the time of a later adjtime() call, and delta is not NULL
|
|
* for the later call, then the earlier adjustment is stopped, but any
|
|
* already completed part of that adjustment is not undone.
|
|
*
|
|
* If olddelta is not NULL, then the buffer that it points to is used to
|
|
* return the amount of time remaining from any previous adjustment that
|
|
* has not yet been completed.
|
|
*
|
|
* NOTE: This is not a POSIX interface but derives from 4.3BSD, System V.
|
|
* It is also supported for Linux compatibility.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int adjtime(FAR const struct timeval *delta, FAR struct timeval *olddelta)
|
|
{
|
|
irqstate_t flags;
|
|
long adjust_usec;
|
|
|
|
if (!delta)
|
|
{
|
|
set_errno(EINVAL);
|
|
return -1;
|
|
}
|
|
|
|
flags = spin_lock_irqsave(&g_clock_lock);
|
|
|
|
adjust_usec = delta->tv_sec * USEC_PER_SEC + delta->tv_usec;
|
|
|
|
if (olddelta)
|
|
{
|
|
olddelta->tv_usec = g_clock_adjust;
|
|
}
|
|
|
|
g_clock_adjust = adjust_usec;
|
|
|
|
spin_unlock_irqrestore(&g_clock_lock, flags);
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: clock_update_wall_time
|
|
****************************************************************************/
|
|
|
|
void clock_update_wall_time(void)
|
|
{
|
|
irqstate_t flags;
|
|
uint64_t counter;
|
|
uint64_t offset;
|
|
int64_t nsec;
|
|
time_t sec;
|
|
int ret;
|
|
|
|
flags = spin_lock_irqsave(&g_clock_lock);
|
|
|
|
ret = up_timer_gettick(&counter);
|
|
if (ret < 0)
|
|
{
|
|
goto errout_in_critical_section;
|
|
}
|
|
|
|
offset = (counter - g_clock_last_counter) & g_clock_mask;
|
|
if (offset == 0)
|
|
{
|
|
goto errout_in_critical_section;
|
|
}
|
|
|
|
nsec = offset * NSEC_PER_TICK;
|
|
sec = nsec / NSEC_PER_SEC;
|
|
nsec -= sec * NSEC_PER_SEC;
|
|
|
|
nsec += g_clock_wall_time.tv_nsec;
|
|
if (nsec >= NSEC_PER_SEC)
|
|
{
|
|
nsec -= NSEC_PER_SEC;
|
|
sec += 1;
|
|
}
|
|
|
|
if (g_clock_adjust != 0 && sec > 0)
|
|
{
|
|
long adjust = NTP_MAX_ADJUST * (long)sec;
|
|
if (g_clock_adjust < adjust && g_clock_adjust > -adjust)
|
|
{
|
|
adjust = g_clock_adjust;
|
|
}
|
|
|
|
nsec += adjust * NSEC_PER_USEC;
|
|
|
|
while (nsec < 0)
|
|
{
|
|
nsec += NSEC_PER_SEC;
|
|
sec -= 1;
|
|
}
|
|
|
|
while (nsec >= NSEC_PER_SEC)
|
|
{
|
|
nsec -= NSEC_PER_SEC;
|
|
sec += 1;
|
|
}
|
|
}
|
|
|
|
g_clock_wall_time.tv_sec += sec;
|
|
g_clock_wall_time.tv_nsec = (long)nsec;
|
|
|
|
g_clock_last_counter = counter;
|
|
|
|
errout_in_critical_section:
|
|
spin_unlock_irqrestore(&g_clock_lock, flags);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: clock_inittimekeeping
|
|
****************************************************************************/
|
|
|
|
void clock_inittimekeeping(FAR const struct timespec *tp)
|
|
{
|
|
irqstate_t flags;
|
|
|
|
flags = spin_lock_irqsave(&g_clock_lock);
|
|
up_timer_getmask(&g_clock_mask);
|
|
|
|
if (tp)
|
|
{
|
|
memcpy(&g_clock_wall_time, tp, sizeof(struct timespec));
|
|
}
|
|
else
|
|
{
|
|
clock_basetime(&g_clock_wall_time);
|
|
}
|
|
|
|
clock_notifier_call_chain(CLOCK_REALTIME, &g_clock_wall_time);
|
|
up_timer_gettick(&g_clock_last_counter);
|
|
spin_unlock_irqrestore(&g_clock_lock, flags);
|
|
}
|
|
|
|
#endif /* CONFIG_CLOCK_TIMEKEEPING */
|