mirror of
https://github.com/apache/nuttx.git
synced 2026-09-30 19:06:22 +00:00
Clamp each wall-clock adjustment in both positive and negative directions, then subtract the applied amount from the remaining adjustment. This makes adjtime converge to zero and prevents large negative adjustments from slewing the clock in the wrong direction. Assisted-by: OpenAI Codex Signed-off-by: yushuailong <yyyusl@qq.com>
314 lines
8.6 KiB
C
314 lines
8.6 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 <nuttx/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 limit = NTP_MAX_ADJUST * (long)sec;
|
|
long adjust = g_clock_adjust;
|
|
|
|
/* Limit the adjustment while preserving its direction. */
|
|
|
|
if (adjust > limit)
|
|
{
|
|
adjust = limit;
|
|
}
|
|
else if (adjust < -limit)
|
|
{
|
|
adjust = -limit;
|
|
}
|
|
|
|
nsec += adjust * NSEC_PER_USEC;
|
|
g_clock_adjust -= adjust;
|
|
|
|
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 */
|