esp-idf/components/newlib/time.c
Konstantin Kondrashov 6f529cbe64 newlib: Add adjtime - makes a gradual adjustment the system clock
This function speeds up or slows down the system clock in order to make a gradual adjustment. This ensures
 that the calendar time reported by the system clock is always monotonically increasing, which might not happen
 if you simply set the clock.

The delta argument specifies a relative adjustment to be made to the clock time. If negative, the system clock is
 slowed down for a while until it has lost this much elapsed time. If positive, the system clock is speeded up for a
 while.

If the olddelta argument is not a null pointer, the adjtime function returns information about any previous time
 adjustment that has not yet completed.

The return value is 0 on success and -1 on failure.

To stop the adjustement, call the function settimeofday(current_time).
2018-05-28 17:36:04 +05:00

374 lines
11 KiB
C

// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed 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.
#include <errno.h>
#include <stdlib.h>
#include <time.h>
#include <reent.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/reent.h>
#include <sys/time.h>
#include <sys/times.h>
#include <sys/lock.h>
#include <rom/rtc.h>
#include "esp_attr.h"
#include "esp_intr_alloc.h"
#include "esp_clk.h"
#include "esp_timer.h"
#include "soc/soc.h"
#include "soc/rtc.h"
#include "soc/rtc_cntl_reg.h"
#include "soc/frc_timer_reg.h"
#include "rom/ets_sys.h"
#include "freertos/FreeRTOS.h"
#include "freertos/xtensa_api.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#if defined( CONFIG_ESP32_TIME_SYSCALL_USE_RTC ) || defined( CONFIG_ESP32_TIME_SYSCALL_USE_RTC_FRC1 )
#define WITH_RTC 1
#endif
#if defined( CONFIG_ESP32_TIME_SYSCALL_USE_FRC1 ) || defined( CONFIG_ESP32_TIME_SYSCALL_USE_RTC_FRC1 )
#define WITH_FRC 1
#endif
#ifdef WITH_RTC
static uint64_t get_rtc_time_us()
{
const uint64_t ticks = rtc_time_get();
const uint32_t cal = esp_clk_slowclk_cal_get();
/* RTC counter result is up to 2^48, calibration factor is up to 2^24,
* for a 32kHz clock. We need to calculate (assuming no overflow):
* (ticks * cal) >> RTC_CLK_CAL_FRACT
*
* An overflow in the (ticks * cal) multiplication would cause time to
* wrap around after approximately 13 days, which is probably not enough
* for some applications.
* Therefore multiplication is split into two terms, for the lower 32-bit
* and the upper 16-bit parts of "ticks", i.e.:
* ((ticks_low + 2^32 * ticks_high) * cal) >> RTC_CLK_CAL_FRACT
*/
const uint64_t ticks_low = ticks & UINT32_MAX;
const uint64_t ticks_high = ticks >> 32;
return ((ticks_low * cal) >> RTC_CLK_CAL_FRACT) +
((ticks_high * cal) << (32 - RTC_CLK_CAL_FRACT));
}
#endif // WITH_RTC
// s_boot_time: time from Epoch to the first boot time
#ifdef WITH_RTC
// when RTC is used to persist time, two RTC_STORE registers are used to store boot time
#elif defined(WITH_FRC)
static uint64_t s_boot_time;
#endif // WITH_RTC
#if defined(WITH_RTC) || defined(WITH_FRC)
static _lock_t s_boot_time_lock;
static _lock_t s_adjust_time_lock;
// stores the start time of the slew
RTC_DATA_ATTR static uint64_t adjtime_start = 0;
// is how many microseconds total to slew
RTC_DATA_ATTR static int64_t adjtime_total_correction = 0;
#define ADJTIME_CORRECTION_FACTOR 6
static uint64_t get_time_since_boot();
#endif
// Offset between FRC timer and the RTC.
// Initialized after reset or light sleep.
#if defined(WITH_RTC) && defined(WITH_FRC)
uint64_t s_microseconds_offset;
#endif
#if defined(WITH_RTC) || defined(WITH_FRC)
static void set_boot_time(uint64_t time_us)
{
_lock_acquire(&s_boot_time_lock);
#ifdef WITH_RTC
REG_WRITE(RTC_BOOT_TIME_LOW_REG, (uint32_t) (time_us & 0xffffffff));
REG_WRITE(RTC_BOOT_TIME_HIGH_REG, (uint32_t) (time_us >> 32));
#else
s_boot_time = time_us;
#endif
_lock_release(&s_boot_time_lock);
}
static uint64_t get_boot_time()
{
uint64_t result;
_lock_acquire(&s_boot_time_lock);
#ifdef WITH_RTC
result = ((uint64_t) REG_READ(RTC_BOOT_TIME_LOW_REG)) + (((uint64_t) REG_READ(RTC_BOOT_TIME_HIGH_REG)) << 32);
#else
result = s_boot_time;
#endif
_lock_release(&s_boot_time_lock);
return result;
}
// This function gradually changes boot_time to the correction value and immediately updates it.
static uint64_t adjust_boot_time()
{
uint64_t boot_time = get_boot_time();
if ((boot_time == 0) || (get_time_since_boot() < adjtime_start)) {
adjtime_start = 0;
}
if (adjtime_start > 0) {
uint64_t since_boot = get_time_since_boot();
// If to call this function once per second, then (since_boot - adjtime_start) will be 1_000_000 (1 second),
// and the correction will be equal to (1_000_000us >> 6) = 15_625 us.
// The minimum possible correction step can be (64us >> 6) = 1us.
// Example: if the time error is 1 second, then it will be compensate for 1 sec / 0,015625 = 64 seconds.
int64_t correction = (since_boot - adjtime_start) >> ADJTIME_CORRECTION_FACTOR;
if (correction > 0) {
adjtime_start = since_boot;
if (adjtime_total_correction < 0) {
if ((adjtime_total_correction + correction) >= 0) {
boot_time = boot_time + adjtime_total_correction;
adjtime_start = 0;
} else {
adjtime_total_correction += correction;
boot_time -= correction;
}
} else {
if ((adjtime_total_correction - correction) <= 0) {
boot_time = boot_time + adjtime_total_correction;
adjtime_start = 0;
} else {
adjtime_total_correction -= correction;
boot_time += correction;
}
}
set_boot_time(boot_time);
}
}
return boot_time;
}
// Get the adjusted boot time.
static uint64_t get_adjusted_boot_time (void)
{
_lock_acquire(&s_adjust_time_lock);
uint64_t adjust_time = adjust_boot_time();
_lock_release(&s_adjust_time_lock);
return adjust_time;
}
// Applying the accumulated correction to boot_time and stopping the smooth time adjustment.
static void adjtime_corr_stop (void)
{
_lock_acquire(&s_adjust_time_lock);
if (adjtime_start != 0){
adjust_boot_time();
adjtime_start = 0;
}
_lock_release(&s_adjust_time_lock);
}
#endif //defined(WITH_RTC) || defined(WITH_FRC)
int adjtime(const struct timeval *delta, struct timeval *outdelta)
{
#if defined( WITH_FRC ) || defined( WITH_RTC )
if(delta != NULL){
int64_t sec = delta->tv_sec;
int64_t usec = delta->tv_usec;
if(llabs(sec) > ((INT_MAX / 1000000L) - 1L)) {
return -1;
}
/*
* If adjusting the system clock by adjtime () is already done during the second call adjtime (),
* and the delta of the second call is not NULL, the earlier tuning is stopped,
* but the already completed part of the adjustment is not canceled.
*/
_lock_acquire(&s_adjust_time_lock);
// If correction is already in progress (adjtime_start != 0), then apply accumulated corrections.
adjust_boot_time();
adjtime_start = get_time_since_boot();
adjtime_total_correction = sec * 1000000L + usec;
_lock_release(&s_adjust_time_lock);
}
if(outdelta != NULL){
_lock_acquire(&s_adjust_time_lock);
adjust_boot_time();
if (adjtime_start != 0) {
outdelta->tv_sec = adjtime_total_correction / 1000000L;
outdelta->tv_usec = adjtime_total_correction % 1000000L;
} else {
outdelta->tv_sec = 0;
outdelta->tv_usec = 0;
}
_lock_release(&s_adjust_time_lock);
}
return 0;
#else
return -1;
#endif
}
void esp_clk_slowclk_cal_set(uint32_t new_cal)
{
#if defined(WITH_RTC)
/* To force monotonic time values even when clock calibration value changes,
* we adjust boot time, given current time and the new calibration value:
* T = boot_time_old + cur_cal * ticks / 2^19
* T = boot_time_adj + new_cal * ticks / 2^19
* which results in:
* boot_time_adj = boot_time_old + ticks * (cur_cal - new_cal) / 2^19
*/
const int64_t ticks = (int64_t) rtc_time_get();
const uint32_t cur_cal = REG_READ(RTC_SLOW_CLK_CAL_REG);
int32_t cal_diff = (int32_t) (cur_cal - new_cal);
int64_t boot_time_diff = ticks * cal_diff / (1LL << RTC_CLK_CAL_FRACT);
uint64_t boot_time_adj = get_boot_time() + boot_time_diff;
set_boot_time(boot_time_adj);
#endif // WITH_RTC
REG_WRITE(RTC_SLOW_CLK_CAL_REG, new_cal);
}
uint32_t esp_clk_slowclk_cal_get()
{
return REG_READ(RTC_SLOW_CLK_CAL_REG);
}
void esp_set_time_from_rtc()
{
#if defined( WITH_FRC ) && defined( WITH_RTC )
// initialize time from RTC clock
s_microseconds_offset = get_rtc_time_us() - esp_timer_get_time();
#endif // WITH_FRC && WITH_RTC
}
uint64_t esp_clk_rtc_time(void)
{
#ifdef WITH_RTC
return get_rtc_time_us();
#else
return 0;
#endif
}
clock_t IRAM_ATTR _times_r(struct _reent *r, struct tms *ptms)
{
clock_t t = xTaskGetTickCount() * (portTICK_PERIOD_MS * CLK_TCK / 1000);
ptms->tms_cstime = 0;
ptms->tms_cutime = 0;
ptms->tms_stime = t;
ptms->tms_utime = 0;
struct timeval tv = {0, 0};
_gettimeofday_r(r, &tv, NULL);
return (clock_t) tv.tv_sec;
}
#if defined( WITH_FRC ) || defined( WITH_RTC )
static uint64_t get_time_since_boot()
{
uint64_t microseconds = 0;
#ifdef WITH_FRC
#ifdef WITH_RTC
microseconds = s_microseconds_offset + esp_timer_get_time();
#else
microseconds = esp_timer_get_time();
#endif // WITH_RTC
#elif defined(WITH_RTC)
microseconds = get_rtc_time_us();
#endif // WITH_FRC
return microseconds;
}
#endif // defined( WITH_FRC ) || defined( WITH_RTC )
int IRAM_ATTR _gettimeofday_r(struct _reent *r, struct timeval *tv, void *tz)
{
(void) tz;
#if defined( WITH_FRC ) || defined( WITH_RTC )
if (tv) {
uint64_t microseconds = get_adjusted_boot_time() + get_time_since_boot();
tv->tv_sec = microseconds / 1000000;
tv->tv_usec = microseconds % 1000000;
}
return 0;
#else
__errno_r(r) = ENOSYS;
return -1;
#endif // defined( WITH_FRC ) || defined( WITH_RTC )
}
int settimeofday(const struct timeval *tv, const struct timezone *tz)
{
(void) tz;
#if defined( WITH_FRC ) || defined( WITH_RTC )
if (tv) {
adjtime_corr_stop();
uint64_t now = ((uint64_t) tv->tv_sec) * 1000000LL + tv->tv_usec;
uint64_t since_boot = get_time_since_boot();
set_boot_time(now - since_boot);
}
return 0;
#else
errno = ENOSYS;
return -1;
#endif
}
int usleep(useconds_t us)
{
const int us_per_tick = portTICK_PERIOD_MS * 1000;
if (us < us_per_tick) {
ets_delay_us((uint32_t) us);
} else {
/* since vTaskDelay(1) blocks for anywhere between 0 and portTICK_PERIOD_MS,
* round up to compensate.
*/
vTaskDelay((us + us_per_tick - 1) / us_per_tick);
}
return 0;
}
unsigned int sleep(unsigned int seconds)
{
usleep(seconds*1000000UL);
return 0;
}
uint32_t system_get_time(void)
{
#if defined( WITH_FRC ) || defined( WITH_RTC )
return get_time_since_boot();
#else
return 0;
#endif
}
uint32_t system_get_current_time(void) __attribute__((alias("system_get_time")));
uint32_t system_relative_time(uint32_t current_time)
{
#if defined( WITH_FRC ) || defined( WITH_RTC )
return get_time_since_boot() - current_time;
#else
return 0;
#endif
}
uint64_t system_get_rtc_time(void)
{
#ifdef WITH_RTC
return get_rtc_time_us();
#else
return 0;
#endif
}