diff --git a/Documentation/components/drivers/index.rst b/Documentation/components/drivers/index.rst index e5dfd879ac8ff..2ad2e1ac7212d 100644 --- a/Documentation/components/drivers/index.rst +++ b/Documentation/components/drivers/index.rst @@ -12,6 +12,7 @@ divided in three classes: block/index.rst special/index.rst thermal/index.rst + timers/oneshot/index.rst .. note:: Device driver support depends on the *in-memory*, *pseudo* diff --git a/Documentation/components/drivers/timers/oneshot/index.rst b/Documentation/components/drivers/timers/oneshot/index.rst new file mode 100644 index 0000000000000..6a29b4ae19b50 --- /dev/null +++ b/Documentation/components/drivers/timers/oneshot/index.rst @@ -0,0 +1,159 @@ +===================== +Oneshot Timer Drivers +===================== + +The NuttX timing subsystem consists of four layers: + + * 1 Hardware Timer Drivers: Includes implementations of various hardware + timer drivers. + * 2 Timer Driver Abstraction: Such as Oneshot and Timer, which provide + timer hardware abstraction. + * 3 OS Timer Interfaces: Timer and Alarm, offering relative and + absolute timer interfaces. + * 4 OS Timer Abstraction: The wdog (watchdog) module manages software timers + and provides a unified timer API to upper layers. + +Here we focus on the oneshot timer driver abstraction. + +Oneshot is the timer driver abstraction that provides: + + * Unified API for different timer hardware. + * Functional correct and optimized timing conversion between + cycle counts and natural time. + +Background +========== + +Computers typically rely on hardware cycle counters that count level changes +from external clock signals. These signals are generated by a crystal +oscillator. The signal then goes through a Phase-Locked Loop (PLL) for +frequency multiplication, and is output as the clock signal to the +hardware cycle counter. + +The counter counting up or counting down—with each level changes, enabling +hardware-based timing. To generate timing interrupts, timer hardware includes +a comparator. It triggers a CPU interrupt when the down-counter reaches zero +or the cycle counter matches a preset value. + +Based on the functions of the timers, we can abstract a minimal set of +capabilities that a timer should provide: + + * 1. Read the current cycle count + * 2. Trigger an event at an absolute cycle count + * 3. Trigger an event after a relative cycle count + +From an OS perspective, The second one and third one are functionally +equivalent assuming the first one is available. By reprogramming the timer, +these can also emulate periodical timers. Although these methods are similar +in expressiveness, timers that use relative delays tend to be less accurate +and efficient than those supporting absolute timing. This is because reading +the current time introduces additional CPU overhead, affecting both +timing-precision and performance. + +Oneshot Drivers API +=================== + +OneShot currently offers new count-based interfaces, while also providing +timespec-based interfaces for compatibility with older drivers. +We strongly recommend using the count-based interface due to its superior +performance. Besides, count-based APIs are easier to implement, as they only +need to focus on reading and writing timer-related registers without +needing to perform error-prone time conversion. + +In count-based interface design, oneshot adopts the following principles: + + * Minimalist design: Significantly simplifies the implementation + for drivers. + * Count-based interfaces: Uses count cycles as the unit for both reading + time and setting timers. + * Supports both absolute and relative timers: Compatible with underlying + timer hardware, regardless of whether it uses absolute or + relative timing. + * No status returns: Since read/write operations on timer hardware should + not fail, any failure should result in an assertion + at the driver level. + * No callbacks or parameters: All expiration callback and parameter + management is handled at the upper layer, + preventing thread-unsafe usage. + +The count-based interface is as follows: + + * ``clkcnt_t (*current)(FAR struct oneshot_lowerhalf_s *lower);`` + * ``void (*start)(FAR struct oneshot_lowerhalf_s *lower, clkcnt_t delay);`` + * ``void (*start_absolute)(FAR struct oneshot_lowerhalf_s *lower, clkcnt_t cnt);`` + * ``void (*cancel)(FAR struct oneshot_lowerhalf_s *lower);`` + * ``clkcnt_t (*max_delay)(FAR struct oneshot_lowerhalf_s *lower);`` + +The above count-based interfaces provide functions for: + + * getting the current timer count, + * starting a relative timer, + * starting an absolute timer, + * canceling a timer event + * and getting the maximum timer delay. + +Note that if the driver uses a count-based API, it should call +``oneshot_count_init`` during initialization to tell the upper-layer +the timer frequency. + +The count-based interfaces are enabled via ``CONFIG_ONESHOT_COUNT``. + +The following are the deprecated timespec interfaces: + + * ``int (*max_delay)(FAR struct oneshot_lowerhalf_s *lower, FAR struct timespec *ts);`` + * ``int (*start)(FAR struct oneshot_lowerhalf_s *lower, FAR const struct timespec *ts);`` + * ``int (*cancel)(FAR struct oneshot_lowerhalf_s *lower, FAR struct timespec *ts);`` + * ``int (*current)(FAR struct oneshot_lowerhalf_s *lower, FAR struct timespec *ts);`` + +They provide functions for: + + * getting the maximum timer delay, + * starting a relative timer, + * canceling a timer event + * and getting the current timer count. + +ClockCount +========== + +The recommended oneshot APIs are all count-based. So how do we handle time +conversion? We provide a unified ClockCount(clockcount.h) layer for fast and +safe time conversions, including: + + * count to timespec + * count to tick + * timespec to count + * tick to count + +We notice that there always at least two divisions in timing conversion. So +clockcount implements two methods to accelerate time conversion: + + 1. Invariant Divisor Division Optimization: Used for converting counts + to seconds or ticks. It can be enabled via ``CONFIG_ONESHOT_FAST_DIVISION``. + This division optimization can transforms a division into: + + * one unsigned high multiplication (UMULH), + * one subtraction, + * one addition, and + * one logical right shift (LShR). + + Please note that Invariant Divisor Division Optimization does not + necessarily provide a performance advantage. It is related to the + overhead of UMULH and UDIV instructions on different CPU platforms. + E.g. On early ARMv8A platforms (Cortex A-53), UMULH took 6 cycles, + which meant that enabling optimization was actually less efficient + than direct division using the UDIV instructions. + + 2. Multiply-Shift Approximate Division: Used to convert delta counts into + nanoseconds or ticks. + + Note this was enabled by default. If extramely precise time conversion + is required, it should be disable. + This method trades off slight precision (a few nanoseconds) for better + performance. However, due to potential multiplication overflow, it is + only suitable for relative time conversions. + The first method is exact, but takes about 6-9 CPU cycles. The + approximate approach requires only one unsigned multiplication and one + LShR, typically consuming around 4 CPU cycles, making it + significantly faster. + +Combining 1 and 2, we can achieve a fast and precise time conversion. diff --git a/arch/tricore/Kconfig b/arch/tricore/Kconfig index 07c479d35e145..25885ded5d96e 100644 --- a/arch/tricore/Kconfig +++ b/arch/tricore/Kconfig @@ -41,6 +41,7 @@ config ARCH_CHIP_TC397 select ARCH_TC3XX select ALARM_ARCH select ONESHOT + select ONESHOT_COUNT ---help--- AURIX TC39x family: TC397 diff --git a/arch/tricore/src/common/tricore_internal.h b/arch/tricore/src/common/tricore_internal.h index e90dbe2551097..b6df70c1ace0f 100644 --- a/arch/tricore/src/common/tricore_internal.h +++ b/arch/tricore/src/common/tricore_internal.h @@ -46,6 +46,8 @@ * Pre-processor Definitions ****************************************************************************/ +#define SCU_FREQUENCY 100000000UL + /* Determine which (if any) console driver to use. If a console is enabled * and no other console device is specified, then a serial console is * assumed. diff --git a/arch/tricore/src/common/tricore_systimer.c b/arch/tricore/src/common/tricore_systimer.c index c3d574dc92efc..e54345962a69a 100644 --- a/arch/tricore/src/common/tricore_systimer.c +++ b/arch/tricore/src/common/tricore_systimer.c @@ -34,6 +34,24 @@ #include "IfxStm.h" +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Since the tricore hardware timer triggers an interrupt only when the + * compare value is equal to the counter, setting a compare value that has + * already timed out will not trigger an interrupt. To avoid missing + * interrupts when setting the timer, we should set a minimum delay. + * The minimum delay is calculated based on the CPU frequency and the timer + * frequency. We assume that the worst-case execution time for setting the + * timer does not exceed 40 CPU cycles, and calculate the minimum timer + * delay accordingly. + * 40 CPU cycles (100ns at 400Mhz) ~ 10 timer cycles (for 100 Mhz timer). + */ + +#define TRICORE_SYSTIMER_MIN_DELAY \ + (40ull * SCU_FREQUENCY / IFX_CFG_CPU_CLOCK_FREQUENCY) + /**************************************************************************** * Private Types ****************************************************************************/ @@ -46,40 +64,7 @@ struct tricore_systimer_lowerhalf_s { struct oneshot_lowerhalf_s lower; - volatile void *tbase; - uint64_t freq; - uint64_t alarm; - spinlock_t lock; -}; - -/**************************************************************************** - * Private Function Prototypes - ****************************************************************************/ - -static int tricore_systimer_max_delay(struct oneshot_lowerhalf_s *lower, - struct timespec *ts); -static int tricore_systimer_start(struct oneshot_lowerhalf_s *lower, - const struct timespec *ts); -static int tricore_systimer_cancel(struct oneshot_lowerhalf_s *lower, - struct timespec *ts); -static int tricore_systimer_current(struct oneshot_lowerhalf_s *lower, - struct timespec *ts); - -/**************************************************************************** - * Private Data - ****************************************************************************/ - -static const struct oneshot_operations_s g_tricore_systimer_ops = -{ - .max_delay = tricore_systimer_max_delay, - .start = tricore_systimer_start, - .cancel = tricore_systimer_cancel, - .current = tricore_systimer_current, -}; - -static struct tricore_systimer_lowerhalf_s g_systimer_lower = -{ - .lower.ops = &g_tricore_systimer_ops, + volatile void *tbase; }; /**************************************************************************** @@ -124,59 +109,87 @@ tricore_systimer_set_timecmp(struct tricore_systimer_lowerhalf_s *priv, * lower An instance of the lower-half oneshot state structure. This * structure must have been previously initialized via a call to * oneshot_initialize(); - * ts The location in which to return the maximum delay. * * Returned Value: - * Zero (OK) is returned on success; a negated errno value is returned - * on failure. + * The maximum delay value. * ****************************************************************************/ -static int tricore_systimer_max_delay(struct oneshot_lowerhalf_s *lower, - struct timespec *ts) +static clkcnt_t tricore_systimer_max_delay(struct oneshot_lowerhalf_s *lower) { - ts->tv_sec = UINT32_MAX; - ts->tv_nsec = NSEC_PER_SEC - 1; - - return 0; + return UINT32_MAX; } /**************************************************************************** * Name: tricore_systimer_start * * Description: - * Start the oneshot timer + * Start the oneshot timer. Note that the tricore systimer is special, the + * IRQ is only triggered when timecmp == mtime, so we should avoid the case + * that we miss the timecmp. * * Input Parameters: * lower An instance of the lower-half oneshot state structure. This * structure must have been previously initialized via a call to * oneshot_initialize(); - * handler The function to call when when the oneshot timer expires. - * arg An opaque argument that will accompany the callback. - * ts Provides the duration of the one shot timer. + * delta Provides the duration of delta count. * * Returned Value: - * Zero (OK) is returned on success; a negated errno value is returned - * on failure. + * None. * ****************************************************************************/ -static int tricore_systimer_start(struct oneshot_lowerhalf_s *lower, - const struct timespec *ts) +static void tricore_systimer_start(struct oneshot_lowerhalf_s *lower, + clkcnt_t delta) { struct tricore_systimer_lowerhalf_s *priv = (struct tricore_systimer_lowerhalf_s *)lower; - uint64_t mtime = tricore_systimer_get_time(priv); + irqstate_t flags; + uint64_t mtime; + + delta = delta < TRICORE_SYSTIMER_MIN_DELAY ? + TRICORE_SYSTIMER_MIN_DELAY : delta; + flags = up_irq_save(); + mtime = tricore_systimer_get_time(priv); - priv->alarm = mtime + ts->tv_sec * priv->freq + - ts->tv_nsec * priv->freq / NSEC_PER_SEC; - if (priv->alarm < mtime) - { - priv->alarm = UINT64_MAX; - } + tricore_systimer_set_timecmp(priv, mtime + delta); - tricore_systimer_set_timecmp(priv, priv->alarm); - return 0; + up_irq_restore(flags); +} + +/**************************************************************************** + * Name: tricore_systimer_start_absolute + * + * Description: + * Start the oneshot timer. Note that the tricore systimer is special, the + * IRQ is only triggered when timecmp == mtime, so we should avoid the case + * that we miss the timecmp. + * + * Input Parameters: + * lower An instance of the lower-half oneshot state structure. This + * structure must have been previously initialized via a call to + * oneshot_initialize(); + * expected Target + * + * Returned Value: + * None. + * + ****************************************************************************/ + +static void +tricore_systimer_start_absolute(struct oneshot_lowerhalf_s *lower, + clkcnt_t expected) +{ + struct tricore_systimer_lowerhalf_s *priv = + (struct tricore_systimer_lowerhalf_s *)lower; + + irqstate_t flags = up_irq_save(); + uint64_t min_expected = tricore_systimer_get_time(priv) + + TRICORE_SYSTIMER_MIN_DELAY; + expected = expected < min_expected ? min_expected : expected; + tricore_systimer_set_timecmp(priv, expected); + + up_irq_restore(flags); } /**************************************************************************** @@ -192,44 +205,18 @@ static int tricore_systimer_start(struct oneshot_lowerhalf_s *lower, * lower Caller allocated instance of the oneshot state structure. This * structure must have been previously initialized via a call to * oneshot_initialize(); - * ts The location in which to return the time remaining on the - * oneshot timer. A time of zero is returned if the timer is - * not running. * * Returned Value: - * Zero (OK) is returned on success. A call to up_timer_cancel() when - * the timer is not active should also return success; a negated errno - * value is returned on any failure. + * None. * ****************************************************************************/ -static int tricore_systimer_cancel(struct oneshot_lowerhalf_s *lower, - struct timespec *ts) +static void tricore_systimer_cancel(struct oneshot_lowerhalf_s *lower) { struct tricore_systimer_lowerhalf_s *priv = (struct tricore_systimer_lowerhalf_s *)lower; - uint64_t mtime; tricore_systimer_set_timecmp(priv, UINT64_MAX); - - mtime = tricore_systimer_get_time(priv); - if (priv->alarm > mtime) - { - uint64_t nsec = (priv->alarm - mtime) * - NSEC_PER_SEC / priv->freq; - - ts->tv_sec = nsec / NSEC_PER_SEC; - ts->tv_nsec = nsec % NSEC_PER_SEC; - } - else - { - ts->tv_sec = 0; - ts->tv_nsec = 0; - } - - priv->alarm = 0; - - return 0; } /**************************************************************************** @@ -242,27 +229,18 @@ static int tricore_systimer_cancel(struct oneshot_lowerhalf_s *lower, * lower Caller allocated instance of the oneshot state structure. This * structure must have been previously initialized via a call to * oneshot_initialize(); - * ts The location in which to return the current time. A time of zero - * is returned for the initialization moment. * * Returned Value: - * Zero (OK) is returned on success, a negated errno value is returned on - * any failure. + * Current timer count. * ****************************************************************************/ -static int tricore_systimer_current(struct oneshot_lowerhalf_s *lower, - struct timespec *ts) +static clkcnt_t tricore_systimer_current(struct oneshot_lowerhalf_s *lower) { struct tricore_systimer_lowerhalf_s *priv = (struct tricore_systimer_lowerhalf_s *)lower; - uint64_t mtime = tricore_systimer_get_time(priv); - uint64_t nsec = mtime / (priv->freq / USEC_PER_SEC) * NSEC_PER_USEC; - ts->tv_sec = nsec / NSEC_PER_SEC; - ts->tv_nsec = nsec % NSEC_PER_SEC; - - return 0; + return tricore_systimer_get_time(priv); } /**************************************************************************** @@ -278,12 +256,31 @@ static int tricore_systimer_interrupt(int irq, void *context, void *arg) { struct tricore_systimer_lowerhalf_s *priv = arg; - tricore_systimer_set_timecmp(priv, UINT64_MAX); + /* We do not need to clear the compare register here. */ + oneshot_process_callback(&priv->lower); return 0; } +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct oneshot_operations_s g_tricore_oneshot_ops = +{ + .current = tricore_systimer_current, + .start = tricore_systimer_start, + .start_absolute = tricore_systimer_start_absolute, + .cancel = tricore_systimer_cancel, + .max_delay = tricore_systimer_max_delay +}; + +static struct tricore_systimer_lowerhalf_s g_tricore_oneshot_lowerhalf = +{ + .lower.ops = &g_tricore_oneshot_ops +}; + /**************************************************************************** * Public Functions ****************************************************************************/ @@ -300,11 +297,13 @@ static int tricore_systimer_interrupt(int irq, void *context, void *arg) struct oneshot_lowerhalf_s * tricore_systimer_initialize(volatile void *tbase, int irq, uint64_t freq) { - struct tricore_systimer_lowerhalf_s *priv = &g_systimer_lower; + struct tricore_systimer_lowerhalf_s *priv = &g_tricore_oneshot_lowerhalf; priv->tbase = tbase; - priv->freq = freq; - spin_lock_init(&priv->lock); + + ASSERT(freq <= UINT32_MAX); + + oneshot_count_init(&priv->lower, (uint32_t)freq); IfxStm_setCompareControl(tbase, IfxStm_Comparator_0, diff --git a/arch/tricore/src/tc3xx/tc3xx_timerisr.c b/arch/tricore/src/tc3xx/tc3xx_timerisr.c index c52f77adf1a40..554cc429765b7 100644 --- a/arch/tricore/src/tc3xx/tc3xx_timerisr.c +++ b/arch/tricore/src/tc3xx/tc3xx_timerisr.c @@ -34,12 +34,6 @@ #include "IfxStm.h" -/**************************************************************************** - * Pre-processor Definitions - ****************************************************************************/ - -#define SCU_FREQUENCY 100000000UL - /**************************************************************************** * Public Functions ****************************************************************************/ diff --git a/drivers/timers/Kconfig b/drivers/timers/Kconfig index a44a7cda9f209..112db9c84fa98 100644 --- a/drivers/timers/Kconfig +++ b/drivers/timers/Kconfig @@ -123,6 +123,24 @@ config ONESHOT if ONESHOT +config ONESHOT_COUNT + bool + default n + ---help--- + This option enables the oneshot implementation to be based on the + new clock device driver interfaces. + +config ONESHOT_FAST_DIVISION + bool # Convert Clock Count Using Invariant-divisor Division + default n + depends on ONESHOT_COUNT + ---help--- + This option will enable the invariant-divisor division optimization in + the clock device driver implementation, which can improve performance + for certain architectures. It is recommended to enable it on 32-bit + architecture that do not support hardware division and 64-bit + architecture that hardware division is slow. + config ALARM_ARCH bool "Alarm Arch Implementation" select ARCH_HAVE_TICKLESS diff --git a/drivers/timers/arch_alarm.c b/drivers/timers/arch_alarm.c index 09adbdb45dae3..7a825b06446b9 100644 --- a/drivers/timers/arch_alarm.c +++ b/drivers/timers/arch_alarm.c @@ -47,20 +47,12 @@ static clock_t g_current_tick; static void oneshot_callback(FAR struct oneshot_lowerhalf_s *lower, FAR void *arg) { - clock_t now = 0; + clock_t now; ONESHOT_TICK_CURRENT(g_oneshot_lower, &now); #ifdef CONFIG_SCHED_TICKLESS nxsched_tick_expiration(now); #else - /* Start the next tick first, in order to minimize latency. Ideally - * the ONESHOT_TICK_START would also return the current tick so that - * the retrieving the current tick and starting the new one could be done - * atomically w. respect to a HW timer - */ - - ONESHOT_TICK_START(g_oneshot_lower, 1); - /* It is always an error if this progresses more than 1 tick at a time. * That would break any timer based on wdog; such timers might timeout * early. Add a DEBUGASSERT here to catch those errors. It is not added @@ -68,13 +60,13 @@ static void oneshot_callback(FAR struct oneshot_lowerhalf_s *lower, * would occur due to HW timers possibly running while CPU is being halted. */ - /* DEBUGASSERT(now - g_current_tick <= 1); */ - - while (now - g_current_tick > 0) + while (!clock_compare(now, g_current_tick)) { g_current_tick++; nxsched_process_timer(); } + + ONESHOT_TICK_ABSOLUTE(g_oneshot_lower, now + 1); #endif } @@ -262,17 +254,7 @@ int weak_function up_alarm_tick_start(clock_t ticks) if (g_oneshot_lower != NULL) { - clock_t now = 0; - clock_t delta; - - ONESHOT_TICK_CURRENT(g_oneshot_lower, &now); - delta = ticks - now; - if ((sclock_t)delta < 0) - { - delta = 0; - } - - ret = ONESHOT_TICK_START(g_oneshot_lower, delta); + ret = ONESHOT_TICK_ABSOLUTE(g_oneshot_lower, ticks); } return ret; diff --git a/include/nuttx/timers/oneshot.h b/include/nuttx/timers/oneshot.h index 642bd138f202b..f72ca4372f335 100644 --- a/include/nuttx/timers/oneshot.h +++ b/include/nuttx/timers/oneshot.h @@ -35,7 +35,9 @@ #include #include +#include #include +#include #include /**************************************************************************** @@ -95,8 +97,8 @@ * ****************************************************************************/ -#define ONESHOT_MAX_DELAY(l,t) (l)->ops->max_delay(l,t) -#define ONESHOT_TICK_MAX_DELAY(l,t) oneshot_tick_max_delay(l,t) +#define ONESHOT_MAX_DELAY(l,t) oneshot_max_delay(l,t) +#define ONESHOT_TICK_MAX_DELAY(l,t) oneshot_tick_max_delay(l,t) /**************************************************************************** * Name: ONESHOT_START @@ -118,9 +120,12 @@ * ****************************************************************************/ -#define ONESHOT_START(l,t) (l)->ops->start(l,t) +#define ONESHOT_START(l,t) oneshot_start(l,t) #define ONESHOT_TICK_START(l,t) oneshot_tick_start(l,t) +#define ONESHOT_ABSOLUTE(l,t) oneshot_start_absolute(l,t) +#define ONESHOT_TICK_ABSOLUTE(l,t) oneshot_tick_absolute(l,t) + /**************************************************************************** * Name: ONESHOT_CANCEL * @@ -145,7 +150,7 @@ * ****************************************************************************/ -#define ONESHOT_CANCEL(l,t) (l)->ops->cancel(l,t) +#define ONESHOT_CANCEL(l,t) oneshot_cancel(l,t) #define ONESHOT_TICK_CANCEL(l,t) oneshot_tick_cancel(l,t) /**************************************************************************** @@ -167,7 +172,7 @@ * ****************************************************************************/ -#define ONESHOT_CURRENT(l,t) (l)->ops->current(l,t) +#define ONESHOT_CURRENT(l,t) oneshot_current(l,t) #define ONESHOT_TICK_CURRENT(l,t) oneshot_tick_current(l,t) /**************************************************************************** @@ -186,11 +191,26 @@ typedef CODE void (*oneshot_callback_t) (FAR struct oneshot_lowerhalf_s *lower, FAR void *arg); -/* The one short operations supported by the lower half driver */ +/* The oneshot operations supported by the lower half driver */ struct timespec; struct oneshot_operations_s { +#ifdef CONFIG_ONESHOT_COUNT + /* New clkcnt interfaces with better performance, overflow-free timing + * conversion, and the theoretical optimal timing accuracy. + */ + + CODE clkcnt_t (*current)(FAR struct oneshot_lowerhalf_s *lower); + CODE void (*start)(FAR struct oneshot_lowerhalf_s *lower, + clkcnt_t delay); + CODE void (*start_absolute)(FAR struct oneshot_lowerhalf_s *lower, + clkcnt_t cnt); + CODE void (*cancel)(FAR struct oneshot_lowerhalf_s *lower); + CODE clkcnt_t (*max_delay)(FAR struct oneshot_lowerhalf_s *lower); +#else + /* Deprecated interfaces, just for compatiable-usage. */ + CODE int (*max_delay)(FAR struct oneshot_lowerhalf_s *lower, FAR struct timespec *ts); CODE int (*start)(FAR struct oneshot_lowerhalf_s *lower, @@ -199,6 +219,7 @@ struct oneshot_operations_s FAR struct timespec *ts); CODE int (*current)(FAR struct oneshot_lowerhalf_s *lower, FAR struct timespec *ts); +#endif }; /* This structure describes the state of the oneshot timer lower-half @@ -216,6 +237,17 @@ struct oneshot_lowerhalf_s FAR oneshot_callback_t callback; FAR void *arg; +#ifdef CONFIG_ONESHOT_COUNT + uint32_t frequency; + + uint32_t cnt2nsec_mult; + uint32_t cnt2nsec_shift; +#endif + +#ifdef CONFIG_ONESHOT_FAST_DIVISION + invdiv_param64_t invdiv_freq; +#endif + /* Private lower half data may follow */ }; @@ -247,71 +279,414 @@ extern "C" * Inline Functions ****************************************************************************/ -static inline -int oneshot_tick_max_delay(FAR struct oneshot_lowerhalf_s *lower, - FAR clock_t *ticks) +#ifdef CONFIG_ONESHOT_COUNT +static inline_function +void oneshot_count_init(FAR struct oneshot_lowerhalf_s *lower, + uint32_t frequency) { - struct timespec ts; - int ret; + clkcnt_t result; + DEBUGASSERT(lower && frequency); - if (lower->ops->max_delay == NULL) - { - return -ENOTSUP; - } + lower->frequency = frequency; - ret = lower->ops->max_delay(lower, &ts); - *ticks = clock_time2ticks(&ts); + clkcnt_best_multshift(frequency, NSEC_PER_SEC, + &lower->cnt2nsec_mult, + &lower->cnt2nsec_shift); + + /* Ensure the maximum error of the mult-shift is less than 5ns. */ + + result = clkcnt_delta_cnt2nsec_fast(frequency, lower->cnt2nsec_mult, + lower->cnt2nsec_shift); + + ASSERT(NSEC_PER_SEC - 5 <= result && NSEC_PER_SEC + 5 >= result); + +# ifdef CONFIG_ONESHOT_FAST_DIVISION + /* invdiv requires the invariant-divsor > 1. */ + + ASSERT(frequency > 1); + + invdiv_init_param64(frequency, &lower->invdiv_freq); +# endif +} + +static inline_function +uint32_t oneshot_delta_cnt2nsec(FAR struct oneshot_lowerhalf_s *lower, + clkcnt_t delta) +{ + DEBUGASSERT(delta <= lower->frequency); + + /* Here we use a multiply-shift method to convert the clock + * count to nanoseconds. This will reduce at least one division + * operation and improve the performance. Note that this is an + * approximate method that trades accuracy for performance, it may lead + * to 1-3 nanoseconds of error when converting the cycles that + * represent less than 1 second. If extremely high resolution time is + * required, then this option should be disabled. + */ + + return clkcnt_delta_cnt2nsec_fast(delta, lower->cnt2nsec_mult, + lower->cnt2nsec_shift); +} + +static inline_function +clock_t oneshot_delta_cnt2tick(FAR struct oneshot_lowerhalf_s *lower, + clkcnt_t delta) +{ + uint32_t nsec; + + DEBUGASSERT(delta <= lower->frequency); + + /* Be careful of using mult-shift fast converting here. + * Since ticks are related to the scheduling, inaccurate converting + * results may lead to wrong scheduling. + */ + + nsec = clkcnt_delta_cnt2nsec_fast(delta, lower->cnt2nsec_mult, + lower->cnt2nsec_shift); + + return div_const(nsec, NSEC_PER_TICK); +} + +static inline_function +uint64_t oneshot_cnt2sec(FAR struct oneshot_lowerhalf_s *lower, + clkcnt_t cnt) +{ +# ifdef CONFIG_ONESHOT_FAST_DIVISION + return clkcnt_delta_cnt2time_invdiv(cnt, 1, &lower->invdiv_freq); +# else + return clkcnt_cnt2sec(cnt, lower->frequency); +# endif +} +#endif + +static inline_function +int oneshot_max_delay(FAR struct oneshot_lowerhalf_s *lower, + FAR struct timespec *ts) +{ + int ret = OK; +#ifdef CONFIG_ONESHOT_COUNT + clkcnt_t max = lower->ops->max_delay(lower); + clkcnt_max_timespec(max, lower->frequency, ts); +#else + ret = lower->ops->max_delay(lower, ts); +#endif return ret; } -static inline -int oneshot_tick_start(FAR struct oneshot_lowerhalf_s *lower, - clock_t ticks) +/**************************************************************************** + * Name: oneshot_current + * + * Description: + * Get the current time. + * + * Input Parameters: + * ops - The oneshot interface. + * lower - The oneshot lowerhalf data. + * ts - The pointer to the current time. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +static inline_function +int oneshot_current(FAR struct oneshot_lowerhalf_s *lower, + FAR struct timespec *ts) { - struct timespec ts; + int ret = OK; +#ifdef CONFIG_ONESHOT_COUNT + clkcnt_t cnt = lower->ops->current(lower); + uint32_t freq = lower->frequency; + uint64_t sec = oneshot_cnt2sec(lower, cnt); + + cnt -= sec * freq; + ts->tv_nsec = oneshot_delta_cnt2nsec(lower, cnt); + ts->tv_sec = sec; +#else + ret = lower->ops->current(lower, ts); +#endif + return ret; +} - if (lower->ops->start == NULL) - { - return -ENOTSUP; - } +/**************************************************************************** + * Name: oneshot_cancel + * + * Description: + * Cancel the timer + * + * Input Parameters: + * ops - The oneshot interface. + * ts - The delta time in timespec. + * + * Returned Value: + * None. + * + ****************************************************************************/ - clock_ticks2time(&ts, ticks); - return lower->ops->start(lower, &ts); +static inline_function +int oneshot_cancel(FAR struct oneshot_lowerhalf_s *lower, + FAR struct timespec *ts) +{ + int ret = OK; +#ifdef CONFIG_ONESHOT_COUNT + lower->ops->cancel(lower); + oneshot_current(lower, ts); +#else + ret = lower->ops->cancel(lower, ts); +#endif + return ret; } -static inline -int oneshot_tick_cancel(FAR struct oneshot_lowerhalf_s *lower, - FAR clock_t *ticks) +/**************************************************************************** + * Name: oneshot_start + * + * Description: + * Set the relative time in timespec to trigger the clockevent. + * + * Input Parameters: + * ops - The oneshot interface. + * ts - The delta time in timespec. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +static inline_function +int oneshot_start(FAR struct oneshot_lowerhalf_s *lower, + FAR const struct timespec *ts) { - struct timespec ts; - int ret; + int ret = OK; +#ifdef CONFIG_ONESHOT_COUNT + clkcnt_t freq = lower->frequency; + clkcnt_t cnt = clkcnt_delta_time2cnt(ts->tv_nsec, freq, NSEC_PER_SEC) + + ts->tv_sec * freq; - if (lower->ops->cancel == NULL) + lower->ops->start(lower, cnt); +#else + ret = lower->ops->start(lower, ts); +#endif + return ret; +} + +/**************************************************************************** + * Name: oneshot_start_absolute + * + * Description: + * Set the absolute time to trigger the clockevent. + * + * Input Parameters: + * ops - The oneshot interface. + * expected - The expected time count. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +static inline_function +int oneshot_start_absolute(FAR struct oneshot_lowerhalf_s *lower, + FAR const struct timespec *ts) +{ + int ret = OK; +#ifdef CONFIG_ONESHOT_COUNT + uint32_t freq = lower->frequency; + clkcnt_t expected = ts->tv_sec * freq + + clkcnt_delta_time2cnt(ts->tv_nsec, freq, NSEC_PER_SEC); + + if (lower->ops->start_absolute) { - return -ENOTSUP; + lower->ops->start_absolute(lower, expected); } + else + { + /* IRQ should be disable or the timer will be fired too late. */ - ret = lower->ops->cancel(lower, &ts); - *ticks = clock_time2ticks(&ts); + irqstate_t flags = up_irq_save(); + clkcnt_t delay = expected - lower->ops->current(lower); + lower->ops->start(lower, delay); + up_irq_restore(flags); + } +#else + struct timespec curr = + { + 0 + }; + + /* Some timer drivers may not have current() function. + * Since only arch_alarm uses the function, it should be OK. + */ + + DEBUGASSERT(lower->ops->current); + ret = lower->ops->current(lower, &curr); + clock_timespec_subtract(ts, &curr, &curr); + ret = lower->ops->start(lower, &curr); +#endif return ret; } -static inline -int oneshot_tick_current(FAR struct oneshot_lowerhalf_s *lower, - FAR clock_t *ticks) +/* Tick-based compatible layer for oneshot */ + +static inline_function +int oneshot_tick_max_delay(FAR struct oneshot_lowerhalf_s *lower, + FAR clock_t *tick) +{ + int ret = OK; +#ifdef CONFIG_ONESHOT_COUNT + clkcnt_t max = lower->ops->max_delay(lower); + *tick = clkcnt_max_tick(max, lower->frequency); +#else + struct timespec ts = + { + 0 + }; + + ret = lower->ops->max_delay(lower, &ts); + *tick = clock_time2ticks(&ts); +#endif + return ret; +} + +/**************************************************************************** + * Name: oneshot_tick_start + * + * Description: + * Set the relative time in ticks to trigger the clockevent. + * + * Input Parameters: + * ops - The oneshot interface. + * tick - The delta time in ticks. + + * Returned Value: + * None. + * + ****************************************************************************/ + +static inline_function +int oneshot_tick_start(FAR struct oneshot_lowerhalf_s *lower, + clock_t tick) { + int ret = OK; +#ifdef CONFIG_ONESHOT_COUNT + clkcnt_t cnt = clkcnt_tick2cnt(tick, lower->frequency); + lower->ops->start(lower, cnt); +#else struct timespec ts; - int ret; + clock_ticks2time(&ts, tick); + ret = lower->ops->start(lower, &ts); +#endif + return ret; +} + +/**************************************************************************** + * Name: oneshot_tick_current + * + * Description: + * Get the current system tick. + * + * Input Parameters: + * ops - The oneshot interface. + * lower - The oneshot lowerhalf data. + * + * Returned Value: + * The current system tick. + * + ****************************************************************************/ + +static inline_function +int oneshot_tick_current(FAR struct oneshot_lowerhalf_s *lower, + FAR clock_t *tick) +{ + int ret = OK; +#ifdef CONFIG_ONESHOT_COUNT + clkcnt_t cnt = lower->ops->current(lower); + uint32_t freq = lower->frequency; + uint64_t sec = oneshot_cnt2sec(lower, cnt); + + cnt -= sec * freq; + *tick = sec * TICK_PER_SEC + oneshot_delta_cnt2tick(lower, cnt); +#else + struct timespec ts = + { + 0 + }; - if (lower->ops->current == NULL) + /* Some timer drivers may not have current() function. + * Since only arch_alarm uses the function, it should be OK. + */ + + DEBUGASSERT(lower->ops->current); + + ret = lower->ops->current(lower, &ts); + *tick = clock_time2ticks_floor(&ts); +#endif + return ret; +} + +static inline_function +int oneshot_tick_absolute(FAR struct oneshot_lowerhalf_s *lower, + clock_t tick) +{ + int ret = OK; +#ifdef CONFIG_ONESHOT_COUNT + clkcnt_t expected = clkcnt_tick2cnt(tick, lower->frequency); + if (lower->ops->start_absolute) { - return -ENOTSUP; + lower->ops->start_absolute(lower, expected); } + else + { + /* IRQ should be disable or the timer will be fired too late. */ - ret = lower->ops->current(lower, &ts); - *ticks = clock_time2ticks_floor(&ts); + irqstate_t flags = up_irq_save(); + clkcnt_t delay = expected - lower->ops->current(lower); + lower->ops->start(lower, delay); + up_irq_restore(flags); + } +#else + struct timespec ts; + clock_ticks2time(&ts, tick); + ret = oneshot_start_absolute(lower, &ts); +#endif + return ret; +} +/**************************************************************************** + * Name: oneshot_tick_cancel + * + * Description: + * Cancel the timer. + * + * Input Parameters: + * ops - The oneshot interface. + * lower - The oneshot lowerhalf data. + * + * Returned Value: + * The current system tick. + * + ****************************************************************************/ + +static inline_function +int oneshot_tick_cancel(FAR struct oneshot_lowerhalf_s *lower, + FAR clock_t *tick) +{ + int ret = OK; +#ifdef CONFIG_ONESHOT_COUNT + lower->ops->cancel(lower); + oneshot_tick_current(lower, tick); +#else + struct timespec ts = + { + 0 + }; + + ret = lower->ops->cancel(lower, &ts); + + /* Converting timespec to ticks may overflow. */ + + *tick = clock_time2ticks_floor(&ts); +#endif return ret; }