1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/drivers/include/periph/ptp.h
Marian Buschsieweke acd9f4cc1a
drivers/periph_ptp: fix adjustment API
The clock adjustment API only used a 16 bit integer for speed correction. This
is to course grained to allow compensating clock drifts at high accuracy.
Using a 32 bit integer instead would allow to fix for a drift of up to
about 1 nanosecond drift per each 5 seconds.

That ought to be enough for anyone! (*cough* *cough*)
2021-02-10 09:55:52 +01:00

409 lines
13 KiB
C

/*
* Copyright (C) 2020 Otto-von-Guericke-Universität Magdeburg
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @defgroup drivers_periph_ptp PTP-Clock
* @ingroup drivers_periph
* @brief Low-level PTP clock peripheral driver
*
* # Introduction
*
* The [Precision Time Protocol (PTP)](https://standards.ieee.org/content/ieee-standards/en/standard/1588-2019.html)
* can be used to synchronize clocks with hardware support in an Ethernet PHY,
* that allows to precisely estimate (and, thus, compensate) network delay
* between time server and client. This allows PTP synchronization to be highly
* accurate (<< 1 µs offset between server and client), but requires hardware
* support on each synchronized node and the PTP server, all nodes to be
* connected to the same* Ethernet network, and (for best results) hardware
* support in all intermediate switches.
*
* # (No) Synchronization Using This API
*
* This API is intended to provide basic access to a PTP hardware clock. This
* does not cover any synchronization. Some PTP hardware clocks (e.g. on the
* STM32) can be used without synchronization by manually setting the time.
* Thus, the PTP clock can be useful even without synchronization implemented.
*
* It is intended that the actual synchronization is implemented externally
* later on. The functions @ref ptp_clock_adjust and @ref ptp_clock_adjust_speed
* are specified with the needs of a synchronization solution in mind.
*
* # Clock vs Timer
*
* This API provides both `ptp_clock_*()` and `ptp_timer_*()` functions, to
* distinguish between the feature set being used. A PTP peripheral might only
* support the feature `periph_ptp`, but not `periph_ptp_timer`.
*
* # (Lack of) Power Considerations
*
* It is assumed that a board connected to a wired network is also powered
* from mains. Additionally, a high-resolution high-accuracy clock that is
* periodically synced over network is just not going to play well with
* low-power application scenarios.
*
* @{
* @file
* @brief Low-level PTP clock peripheral driver interface definitions
*
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
*/
#ifndef PERIPH_PTP_H
#define PERIPH_PTP_H
#include <stdint.h>
#include "periph_cpu.h"
#include "timex.h"
#ifdef __cplusplus
extern "C" {
#endif
/* verify settings from periph_cpu.h */
#if !defined(HAVE_PTP_CLOCK_READ) && !defined(HAVE_PTP_CLOCK_READ_U64)
#error "Neither ptp_clock_read() nor ptp_clock_read_u64() implemented"
#endif
#if !defined(HAVE_PTP_CLOCK_SET) && !defined(HAVE_PTP_CLOCK_SET_U64)
#error "Neither ptp_clock_set() nor ptp_clock_set_u64() implemented"
#endif
#if \
!defined(HAVE_PTP_TIMER_SET_ABSOLUTE) && \
!defined(HAVE_PTP_TIMER_SET_ABSOLUTE_U64) && \
IS_USED(MODULE_PERIPH_PTP_TIMER)
#error "Neither ptp_timer_set_absolute() nor ptp_timer_set_absolute_u64() implemented"
#endif
/**
* @brief Unsigned integer type to store seconds since epoch for use in PTP
*
* The PTP protocol defines the seconds part of PTP timestamps as an 48 bit
* unsigned integer. We go for 32 bit for now (works until year 2106) and will
* later extend this type to 64 bit. Users are highly encouraged to use this
* type instead of `uint32_t`, if they intent that their software still works
* in a couple of decades.
*/
typedef uint32_t ptp_seconds_t;
/**
* @brief A PTP timestamp in seconds + nanoseconds since UNIX epoch
*
* According to IEEE 1588-2019 specification in section "5.3.3 Timestamp",
* timestamps are represented as seconds and nanoseconds since epoch. For the
* seconds parts an 48 bit unsigned integer is used in the protocol and a 32 bit
* unsigned integer for the nanoseconds.
*/
typedef struct {
ptp_seconds_t seconds; /**< Seconds since UNIX epoch */
uint32_t nanoseconds; /**< Nanoseconds part */
} ptp_timestamp_t;
/**
* @brief Compare two PTP timestamps
*
* @param a First timestamp
* @param b Second timestamp
*
* @retval -1 @p a < @p b
* @retval 0 @p a == @p b
* @retval 1 @p a > @p b
*/
static inline int ptp_cmp(const ptp_timestamp_t *a, const ptp_timestamp_t *b)
{
if (a->seconds < b->seconds) {
return -1;
}
if (a->seconds > b->seconds) {
return 1;
}
if (a->nanoseconds < b->nanoseconds) {
return -1;
}
if (a->nanoseconds > b->nanoseconds) {
return 1;
}
return 0;
}
/**
* @brief Add a given offset onto the given timestamp
*
* @param[in,out] t Timestamp to add offset to
* @param[in] offset Offset to apply
*/
static inline void ptp_add(ptp_timestamp_t *t, int64_t offset)
{
/* Modulo for negative numbers should be avoided */
if (offset >= 0) {
uint64_t abs_offset = offset;
t->seconds += abs_offset / NS_PER_SEC;
t->nanoseconds += abs_offset % NS_PER_SEC;
/* correct overflow of nanosecond part */
if (t->nanoseconds >= NS_PER_SEC) {
t->nanoseconds -= NS_PER_SEC;
t->seconds++;
}
}
else {
uint64_t abs_offset = -offset;
t->seconds -= abs_offset / NS_PER_SEC;
t->nanoseconds -= abs_offset % NS_PER_SEC;
/* correct underflow of nanosecond part */
if (t->nanoseconds > NS_PER_SEC) {
t->nanoseconds += NS_PER_SEC;
t->seconds--;
}
}
}
/**
* @brief Convert time from nanoseconds since epoch to ptp_timestamp_t format
*
* @param[out] dest The timestamp will be written here
* @param[in] ns_since_epoch Time in nanoseconds since epoch to convert
*/
static inline void ptp_ns2ts(ptp_timestamp_t *dest, uint64_t ns_since_epoch)
{
dest->seconds = ns_since_epoch / NS_PER_SEC;
dest->nanoseconds = ns_since_epoch % NS_PER_SEC;
}
/**
* @brief Convert time from nanoseconds since epoch to ptp_timestamp_t format
*
* @param[in] t Time to convert to nanoseconds since epoch
*
* @return The time specified by @p t in nanoseconds since epoch
*/
static inline uint64_t ptp_ts2ns(const ptp_timestamp_t *t)
{
return t->seconds * NS_PER_SEC + t->nanoseconds;
}
/**
* @brief Initialize the given PTP peripheral
*
* @note Implementations of this functions have to use `assert()` to make
* the configuration is valid.
*
* @pre This function must be called at most once
*
* After calling this, the PTP clock (and the PTP timer, if the feature
* `periph_ptp_timer` is used in addition to `periph_ptp_clock`) can be used.
*/
void ptp_init(void);
/**
* @brief Adjust the PTP clock speed as given
*
* @param correction Specification of the clock speed adjustment
*
* @note This implies feature `periph_ptp_speed_adjustment`
*
* The clock speed is adjusted in regard to its nominal clock speed. This means
* that calls with the same value in @p correction are idempotent.
*
* 1. A call with @p correction set to `0` restores the nominal clock speed.
* 2. A call with a positive value for @p correction speeds the clock up
* by `correction / (1 << 32)` (so up to ~50% for `INT32_MAX`).
* 3. A call with a negative value for @p correction slows the clock down by
* `-correction / (1 << 32)` (so up to 50% for `INT32_MIN`).
*
* This allows the clock speed to be adjusted in steps ± 2.3284E-08 % in relation
* to its nominal clock speed, thus, allowing to counter systematic clock drift.
* In addition, this is intended to "smoothly" synchronize the clock over time
* to avoid jumps in the system time. (Especially calling @ref ptp_clock_adjust
* with negative values might be something to avoid, when applications are not
* prepared with clocks going backwards.)
*/
void ptp_clock_adjust_speed(int32_t correction);
/**
* @brief Adjust the PTP clock as given
*
* @param offset Offset to add onto current system time
*
* Same as `ptp_clock_set_u64(ptp_clock_read_u64() + offset)`, but implemented
* to introduce as little error as possible while setting the offset. This
* is intended to be used by clock synchronization implementations.
*/
void ptp_clock_adjust(int64_t offset);
#if defined(HAVE_PTP_CLOCK_READ) || defined(DOXYGEN)
/**
* @brief Get the current system time as PTP timestamp
*
* @param[out] timestamp The timestamp will be written here
* @pre The PTP clock is initialized
*/
void ptp_clock_read(ptp_timestamp_t *timestamp);
#endif /* HAVE_PTP_CLOCK_READ */
#if defined(HAVE_PTP_CLOCK_READ_U64) || defined(DOXYGEN)
/**
* @brief Get the current system time in nanosecond since UNIX epoch
*
* @return Nanoseconds since 1. 1. 1970 0:00:00 UTC
*
* @pre The PTP clock is initialized
*
* @note An `uint64_t` allows nanosecond timestamps within 1970-01-01 and
* 2554-07-21 to be represented.
*/
uint64_t ptp_clock_read_u64(void);
#endif /* HAVE_PTP_CLOCK_READ_U64 */
#if defined(HAVE_PTP_CLOCK_SET) || defined(DOXYGEN)
/**
* @brief Set the current system time
*
* @param time The new system time
*
* @pre The PTP clock is initialized
*/
void ptp_clock_set(const ptp_timestamp_t *time);
#endif /* HAVE_PTP_CLOCK_SET */
#if defined(HAVE_PTP_CLOCK_SET_U64) || defined(DOXYGEN)
/**
* @brief Set the current system time in nanosecond since UNIX epoch
*
* @param ns_since_epoch New time to set
*
* @pre The PTP clock is initialized
*/
void ptp_clock_set_u64(uint64_t ns_since_epoch);
#endif /* HAVE_PTP_CLOCK_SET_U64 */
/**
* @brief External function to call when the PTP clock timer fired
*
* @note This function needs to be implemented by the *user* of this API
* @note This function implies feature `periph_ptp_timer`
*
* Since at most one PTP clock is expected on each board, we can avoid the
* overhead of indirect function calls here and let users of this API just
* implement this function.
*/
void ptp_timer_cb(void);
#if defined(HAVE_PTP_TIMER_SET_ABSOLUTE) || defined(DOXYGEN)
/**
* @brief Set an absolute timeout value, possibly overwriting an existing
* timeout
*
* @param target Timestamp when the timeout should fire
*
* @note Only a single timeout is supported by the PTP timer
* @note This function implies feature `periph_ptp_timer`
*
* @details If the target time is in the past or equal to the current time, the
* IRQ should trigger right away.
*/
void ptp_timer_set_absolute(const ptp_timestamp_t *target);
#endif /* HAVE_PTP_TIMER_SET_ABSOLUTE */
#if defined(HAVE_PTP_TIMER_SET_ABSOLUTE_U64) || defined(DOXYGEN)
/**
* @brief Set an absolute timeout value, possibly overwriting an existing
* timeout
*
* @param target Timestamp when the timeout should fire (ns since epoch)
*
* @note Only a single timeout is supported by the PTP timer
* @note This function implies feature `periph_ptp_timer`
*
* @details If the target time is in the past or equal to the current time, the
* IRQ should trigger right away.
*/
void ptp_timer_set_absolute_u64(uint64_t target);
#endif /* HAVE_PTP_TIMER_SET_ABSOLUTE_U64 */
/**
* @brief Set an relative timeout value, possibly overwriting an existing
* timeout
*
* @param target Number of nanoseconds after which the timeout should fire
*
* @note Only a single timeout is supported by the PTP timer
* @note This function implies feature `periph_ptp_timer`
*/
void ptp_timer_set_u64(uint64_t target);
/**
* @brief Clears any pending timeout on the PTP timer
*
* @note This function implies feature `periph_ptp_timer`
*/
void ptp_timer_clear(void);
/* Fallback implementations (the driver can implement either the
* functions using `ptp_timestamp_t` or `uint64_t`, the other flavor will
* be provided on top here): */
#ifndef HAVE_PTP_CLOCK_READ
static inline void ptp_clock_read(struct ptp_timestamp_t *timestamp)
{
ptp_ns2ts(timestamp, ptp_clock_read_u64());
}
#endif /* !HAVE_PTP_CLOCK_READ */
#ifndef HAVE_PTP_CLOCK_READ_U64
static inline uint64_t ptp_clock_read_u64(void)
{
ptp_timestamp_t ts;
ptp_clock_read(&ts);
return ptp_ts2ns(&ts);
}
#endif /* !HAVE_PTP_CLOCK_READ_U64 */
#ifndef HAVE_PTP_CLOCK_SET
static inline void ptp_clock_set(const ptp_timestamp_t *time)
{
ptp_clock_set_u64(ptp_ts2ns(time));
}
#endif /* !HAVE_PTP_CLOCK_SET */
#ifndef HAVE_PTP_CLOCK_SET_U64
static inline void ptp_clock_set_u64(uint64_t ns_since_epoch)
{
ptp_timestamp_t time;
ptp_ns2ts(&time, ns_since_epoch);
ptp_clock_set(&time);
}
#endif /* !HAVE_PTP_CLOCK_SET_U64 */
#ifndef HAVE_PTP_TIMER_SET_ABSOLUTE
static inline void ptp_timer_set_absolute(const ptp_timestamp_t *target)
{
ptp_timer_set_absolute_u64(ptp_ts2ns(target));
}
#endif /* !HAVE_PTP_TIMER_SET_ABSOLUTE */
#ifndef HAVE_PTP_TIMER_SET_ABSOLUTE_U64
static inline void ptp_timer_set_absolute_u64(uint64_t target)
{
ptp_timestamp_t ts;
ptp_ns2ts(&ts, target);
ptp_timer_set_absolute(&ts);
}
#endif /* !HAVE_PTP_TIMER_SET_ABSOLUTE_U64 */
#ifdef __cplusplus
}
#endif
#endif /* PERIPH_PTP_H */
/** @} */