diff --git a/drivers/Makefile.dep b/drivers/Makefile.dep index d0c97ea903..a6734daa54 100644 --- a/drivers/Makefile.dep +++ b/drivers/Makefile.dep @@ -102,6 +102,10 @@ ifneq (,$(filter nrfmin,$(USEMODULE))) USEMODULE += netif endif +ifneq (,$(filter periph_ptp_timer periph_ptp_speed_adjustment,$(FEATURES_USED))) + FEATURES_REQUIRED += periph_ptp +endif + ifneq (,$(filter qmc5883l_%,$(USEMODULE))) USEMODULE += qmc5883l endif diff --git a/drivers/include/periph/ptp.h b/drivers/include/periph/ptp.h new file mode 100644 index 0000000000..abe4245efa --- /dev/null +++ b/drivers/include/periph/ptp.h @@ -0,0 +1,408 @@ +/* + * 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 + */ + +#ifndef PERIPH_PTP_H +#define PERIPH_PTP_H + +#include + +#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 << 16)` (so up to ~50% for `INT16_MAX`). + * 3. A call with a negative value for @p correction slows the clock down by + * `-correction / (1 << 16)` (so up to 50% for `INT16_MIN`). + * + * This allows the clock speed to be adjusted in steps ± 0.00153% 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(int16_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 */ +/** @} */ diff --git a/drivers/periph_common/init.c b/drivers/periph_common/init.c index c3f6bd4463..5d328ab22e 100644 --- a/drivers/periph_common/init.c +++ b/drivers/periph_common/init.c @@ -46,6 +46,9 @@ #ifdef MODULE_PERIPH_INIT_WDT #include "periph/wdt.h" #endif +#ifdef MODULE_PERIPH_INIT_PTP +#include "periph/ptp.h" +#endif #endif /* MODULE_PERIPH_INIT */ void periph_init(void) @@ -92,5 +95,9 @@ void periph_init(void) wdt_init(); #endif +#if defined(MODULE_PERIPH_INIT_PTP) + ptp_init(); +#endif + #endif /* MODULE_PERIPH_INIT */ } diff --git a/drivers/periph_common/ptp.c b/drivers/periph_common/ptp.c new file mode 100644 index 0000000000..4d0f2c8edc --- /dev/null +++ b/drivers/periph_common/ptp.c @@ -0,0 +1,50 @@ +/* + * 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. + */ + +/** + * @ingroup drivers_periph_ptp + * @{ + * + * @file + * @brief Common code for PTP clocks and timers + * + * @author Marian Buschsieweke + * + * @} + */ +#include "kernel_defines.h" +#if IS_USED(MODULE_PERIPH_PTP_TIMER) +#include "irq.h" +#include "periph/ptp.h" + +#if !defined(HAVE_PTP_TIMER_SET_U64) +void ptp_timer_set_u64(uint64_t target) +{ + unsigned irq_state = irq_disable(); + if (IS_ACTIVE(HAVE_PTP_TIMER_SET_ABSOLUTE) && IS_ACTIVE(HAVE_PTP_CLOCK_READ)) { + /* This is slightly more efficient when the PTP clock implementation + * uses ptp_timestamp_t natively */ + ptp_timestamp_t now; + ptp_clock_read(&now); + now.seconds += target / NS_PER_SEC; + now.nanoseconds += target % NS_PER_SEC; + while (now.nanoseconds >= NS_PER_SEC) { + now.seconds++; + now.nanoseconds -= NS_PER_SEC; + } + ptp_timer_set_absolute(&now); + } + else { + ptp_timer_set_absolute_u64(ptp_clock_read_u64() + target); + } + irq_restore(irq_state); +} +#endif /* !defined(HAVE_PTP_TIMER_SET_U64) */ +#else +typedef int dont_be_pedantic; +#endif diff --git a/kconfigs/Kconfig.features b/kconfigs/Kconfig.features index 5c3a5f2e5e..598d539f06 100644 --- a/kconfigs/Kconfig.features +++ b/kconfigs/Kconfig.features @@ -205,6 +205,21 @@ config HAS_PERIPH_PM help Indicates that a Power Management (PM) peripheral is present. +config HAS_PERIPH_PTP + bool + help + Indicates that a PTP clock is present. + +config HAS_PERIPH_PTP_SPEED_ADJUSTMENT + bool + help + Indicates that the PTP clock speed can be adjust. This can be used for clock drift correction and synchronization. + +config HAS_PERIPH_PTP_TIMER + bool + help + Indicates that the PTP clock can be used as timer. + config HAS_PERIPH_PWM bool help