/**************************************************************************** * apps/testing/ostest/hrtimer.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 #include #include #include #include #include #include #include "ostest.h" /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /* HRTimer test constants */ #define HRTIMER_TEST_RAND_ITER (1024 * 2) /* Random delay iterations */ #define HRTIMER_TEST_CSECTION 1024 /* Critical section iterations */ #define HRTIMER_TEST_THREAD_NR (CONFIG_SMP_NCPUS * 8) /* Test threads */ /* Tolerable latency for timer expiration * * QEMU is a virtual platform, vCPUs can be preempted by any * high priority thread. This can cause the timer to be triggered * later than expected. This is especially true on QEMU because it * has a lot of overhead. The timer resolution is also less precise * than on real hardware. Using a larger latency ensures that tests * do not fail due to timing inaccuracies. * * On real hardware (verified on the a2g-tc397-5v-tft board), this * latency can be reduced to less than 5 ns because timers are precise * and deterministic. */ #define HRTIMER_TEST_TOLERENT_LATENCY (10 * NSEC_PER_MSEC) /* Helper macro for nanosecond delay */ #define hrtimer_test_ndelay(delay_ns) usleep((delay_ns) / 1000 + 1) /**************************************************************************** * Private Types ****************************************************************************/ /* Structure for HRTimer test tracking */ typedef struct hrtimer_test_s { struct hrtimer_s timer; /* HRTimer instance */ spinlock_t lock; /* Spinlock for thread-safe operations */ volatile uint64_t timestamp; /* Previous timestamp in nanoseconds */ volatile uint64_t count; /* Number of timer expirations */ uint64_t period; /* Expected period between expirations */ volatile uint8_t state; /* Test state (used for synchronization tests) */ } hrtimer_test_t; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: hrtimer_test_callback_oneshot * * Description: * One-shot timer callback function. This callback is invoked when a * one-shot HRTimer expires. It records the exact timestamp and increments * the expiration counter. * * Input Parameters: * timer - Pointer to the HRTimer instance * expired_ns - Time when the timer expired (unused) * * Returned Value: * 0 (oneshot hrtimer) * ****************************************************************************/ static uint64_t hrtimer_test_callback_oneshot(FAR const hrtimer_t *timer, uint64_t expired_ns) { FAR hrtimer_test_t *param = (FAR hrtimer_test_t *)timer; /* Record the exact timestamp when the callback was triggered */ param->timestamp = clock_systime_nsec(); /* Increment the callback count */ param->count++; return 0; } /**************************************************************************** * Name: hrtimer_test_checkdelay * * Description: * Check timer delay against tolerance threshold. This function verifies * that the actual timer expiration time is within the acceptable * tolerance. * It warns if the delay exceeds the threshold. * * Input Parameters: * timestamp - Actual expiration timestamp (nanoseconds) * expected - Expected expiration timestamp (nanoseconds) * * Returned Value: * None. * ****************************************************************************/ static void hrtimer_test_checkdelay(uint64_t timestamp, uint64_t expected) { int64_t diff = timestamp - expected; /* Ensure the hrtimer trigger time is not earlier than expected. */ ASSERT(diff >= 0); /* If the timer latency exceeds the tolerance, print a warning. */ if (diff > HRTIMER_TEST_TOLERENT_LATENCY) { printf("hrtimer_test: [WARNING] hrtimer latency %" PRId64 " ns is too late (> %u ns)\n", diff, (unsigned)HRTIMER_TEST_TOLERENT_LATENCY); } } /**************************************************************************** * Name: hrtimer_test_oneshot * * Description: * Test one-shot HRTimer functionality. This function tests a one-shot * HRTimer by starting it with a specified delay and verifying that it * expires at the expected time. * * Input Parameters: * param - Test context structure containing timer and state information * delay - Delay in nanoseconds for the one-shot timer * * Returned Value: * None. * ****************************************************************************/ static void hrtimer_test_oneshot(FAR hrtimer_test_t *param, uint64_t delay) { uint64_t count; uint64_t now; FAR hrtimer_t *timer = ¶m->timer; printf("hrtimer_test_oneshot: delay = %" PRIu64 " ns\n", delay); /* Save the current callback count and system time */ count = param->count; now = clock_systime_nsec(); /* Start the one-shot timer */ ASSERT(hrtimer_start(timer, hrtimer_test_callback_oneshot, delay + now, HRTIMER_MODE_ABS) == OK); /* Wait until the callback is triggered exactly once */ while (count + 1 != param->count) { hrtimer_test_ndelay(delay); } /* Verify the delay is within acceptable tolerance */ hrtimer_test_checkdelay(param->timestamp, now + delay); /* Cancel the timer to clean up */ hrtimer_cancel_sync(timer); } /**************************************************************************** * Name: hrtimer_test_maximum * * Description: * Test HRTimer with maximum delay value. This function tests the behavior * of HRTimer when started with the maximum possible delay (UINT64_MAX * nanoseconds). The timer should not expire within a reasonable test * period. * * Input Parameters: * param - Test context structure containing timer and state information * * Returned Value: * None. * ****************************************************************************/ static void hrtimer_test_maximum(FAR hrtimer_test_t *param) { uint64_t count; uint64_t rest; FAR hrtimer_t *timer = ¶m->timer; printf("hrtimer_test_maximum: testing with UINT64_MAX delay\n"); count = param->count; /* Start the hrtimer with maximum delay */ ASSERT(hrtimer_start(timer, hrtimer_test_callback_oneshot, UINT64_MAX, HRTIMER_MODE_REL) == OK); /* Sleep for at least 100ms to verify timer doesn't expire */ hrtimer_test_ndelay(USEC_PER_SEC / 100); /* Ensure hrtimer has not expired (count should remain unchanged) */ ASSERT(count == param->count); /* Get the remaining time until expiration */ rest = hrtimer_gettime(timer); /* Verify the remaining time is still large (less than UINT64_MAX) */ ASSERT(rest < UINT64_MAX); /* Cancel the timer */ ASSERT(hrtimer_cancel_sync(timer) == OK); printf("hrtimer_test_maximum: remaining time = %" PRIu64 " ns\n", rest); } /**************************************************************************** * Name: hrtimer_test_rand * * Description: * Test HRTimer with random delays. This function tests HRTimer behavior * with random delay values within a specified range. It performs multiple * iterations, testing both timer expiration and cancellation scenarios. * * Input Parameters: * param - Test context structure containing timer and state information * rand_ns - Maximum random delay value in nanoseconds * ****************************************************************************/ static void hrtimer_test_rand(FAR hrtimer_test_t *param, uint64_t rand_ns) { uint64_t count; uint64_t now; unsigned int idx; uint64_t delay; irqstate_t flags; FAR hrtimer_t *timer = ¶m->timer; printf("hrtimer_test_rand: max delay = %" PRIu64 " ns\n", rand_ns); /* Perform multiple iterations with random delays */ for (idx = 0; idx < HRTIMER_TEST_RAND_ITER; idx++) { /* Generate a random delay within the specified range */ delay = rand() % rand_ns; ASSERT(timer->func == NULL); /* Enter critical section if the callback count is odd */ count = param->count; if (count % 2u) { flags = up_irq_save(); } now = clock_systime_nsec(); ASSERT(hrtimer_start(timer, hrtimer_test_callback_oneshot, delay, HRTIMER_MODE_REL) == 0); if (count % 2u) { up_irq_restore(flags); } /* Decide to wait for the callback or cancel the hrtimer */ if (delay % 2u) { /* Wait for the callback */ while (count + 1u != param->count) { hrtimer_test_ndelay(delay); } /* Check the delay if the callback count is odd */ if (count % 2u) { hrtimer_test_checkdelay(param->timestamp, now + delay); } } hrtimer_cancel_sync(timer); ASSERT(timer->func == NULL); } hrtimer_cancel_sync(timer); } /**************************************************************************** * Name: hrtimer_test_cancel_callback * * Description: * Callback function for cancel test. This callback is used in the cancel * test to simulate a long-running callback that can be interrupted by * timer cancellation. * * Input Parameters: * timer - Pointer to the HRTimer instance * expired_ns - Time when the timer expired * * Returned Value: * 0 (oneshot hrtimer) * ****************************************************************************/ static uint64_t hrtimer_test_cancel_callback(FAR const hrtimer_t *timer, uint64_t expired_ns) { FAR hrtimer_test_t *param = (FAR hrtimer_test_t *)timer; FAR spinlock_t *lock = ¶m->lock; uint64_t delay = 0; irqstate_t flags = spin_lock_irqsave(lock); /* Calculate random sleep delay based on period */ delay = expired_ns % param->period; /* Check if the expiration time matches (prevents duplicate processing) */ if (expired_ns == timer->expired) { param->timestamp = clock_systime_nsec(); /* Increment the callback count */ param->count++; } spin_unlock_irqrestore(lock, flags); /* Simulate work with random delay */ up_ndelay(delay); return 0; } /**************************************************************************** * Name: hrtimer_test_rand_cancel * * Description: * Test HRTimer cancellation with random delays. This function tests * HRTimer cancellation behavior with random delay values. It verifies * that timers can be properly cancelled and that callbacks behave * correctly when cancellation occurs. * * Input Parameters: * param - Test context structure containing timer and state information * rand_ns - Maximum random delay value in nanoseconds * ****************************************************************************/ static void hrtimer_test_rand_cancel(FAR hrtimer_test_t *param, uint64_t rand_ns) { uint64_t now; unsigned int idx; uint64_t count; uint64_t delay; irqstate_t flags; spinlock_t *lock = ¶m->lock; printf("hrtimer_test_rand_cancel: max delay = %" PRIu64 " ns\n", rand_ns); param->period = rand_ns; /* Perform multiple iterations with random delays */ for (idx = 0; idx < HRTIMER_TEST_RAND_ITER; idx++) { /* Generate a random delay within the specified range */ delay = rand() % rand_ns; flags = spin_lock_irqsave(lock); now = clock_systime_nsec(); count = param->count; ASSERT(hrtimer_start(¶m->timer, hrtimer_test_cancel_callback, delay, HRTIMER_MODE_REL) == 0); spin_unlock_irqrestore(lock, flags); /* Decide to wait for the callback or cancel the hrtimer */ if (delay % 2u) { /* Wait for the callback to finish */ while (param->count != count + 1u) { hrtimer_test_ndelay(delay); } hrtimer_test_checkdelay(param->timestamp, now + delay); } hrtimer_cancel(¶m->timer); } hrtimer_cancel_sync(¶m->timer); } /**************************************************************************** * Name: hrtimer_test_callback_period * * Description: * Periodic timer callback function. This callback is invoked when a * periodic HRTimer expires. It records the timestamp, increments the * count, and returns the interval for the next expiration. * * Input Parameters: * timer - Pointer to the HRTimer instance * expired_ns - Time when the timer expired (unused) * * Returned Value: * The interval in nanoseconds for the next expiration * ****************************************************************************/ static uint64_t hrtimer_test_callback_period(FAR const hrtimer_t *timer, uint64_t expired_ns) { FAR hrtimer_test_t *param = (FAR hrtimer_test_t *)timer; uint64_t interval = param->period; param->count++; param->timestamp = clock_systime_nsec(); return interval; } /**************************************************************************** * Name: hrtimer_test_period * * Description: * Test periodic HRTimer functionality. This function tests a periodic * HRTimer by starting it with a specified interval and verifying that * it triggers multiple times as expected. * * Input Parameters: * param - Test context structure containing timer and state information * delay_ns - Period interval in nanoseconds * iters - Expected number of timer expirations * * Returned Value: * None. * ****************************************************************************/ static void hrtimer_test_period(FAR hrtimer_test_t *param, uint64_t delay_ns, unsigned int iters) { uint64_t timestamp; uint64_t count = param->count; FAR hrtimer_t *timer = ¶m->timer; printf("hrtimer_test_period: period = %" PRIu64 " ns, iters = %u\n", delay_ns, iters); param->period = delay_ns; ASSERT(param->period > 0); timestamp = clock_systime_nsec(); ASSERT(hrtimer_start(timer, hrtimer_test_callback_period, delay_ns, HRTIMER_MODE_REL) == OK); /* Wait for expected number of periods */ hrtimer_test_ndelay(iters * delay_ns); hrtimer_cancel_sync(timer); ASSERT(timer->func == NULL); /* Report results */ printf("hrtimer_test_period: triggered %" PRIu64 " times, " "elapsed nsec %" PRIu64 "\n", param->count - count, param->timestamp - timestamp); /* Warn if fewer expirations than expected */ if (param->count - count < iters) { printf("hrtimer_test_period: [WARNING] periodical hrtimer " "triggered times < %u\n", iters); } } #ifdef CONFIG_SMP /**************************************************************************** * Name: hrtimer_test_callback_crita * * Description: * Critical section test callback A. This callback is used to test * critical section protection in SMP systems. It verifies that timer * callbacks can safely access shared state. * * Input Parameters: * timer - Pointer to the HRTimer instance * expired_ns - Time when the timer expired (unused) * * Returned Value: * 0 (oneshot hrtimer) * ****************************************************************************/ static uint64_t hrtimer_test_callback_crita(FAR const hrtimer_t *timer, uint64_t expired_ns) { FAR hrtimer_test_t *param = (FAR hrtimer_test_t *)timer; /* Change status if in initial state */ if (param->state == 0) { param->state = 1; param->count++; } /* Verify state wasn't changed by another critical section */ ASSERT(param->state == 1); param->state = 0; return 0; } /**************************************************************************** * Name: hrtimer_test_callback_critb * * Description: * Critical section test callback B. This callback is used to test * critical section protection in SMP systems. It verifies that timer * callbacks can safely access shared state. * * Input Parameters: * timer - Pointer to the HRTimer instance * expired_ns - Time when the timer expired (unused) * * Returned Value: * 0 (oneshot hrtimer) * ****************************************************************************/ static uint64_t hrtimer_test_callback_critb(FAR const hrtimer_t *timer, uint64_t expired_ns) { FAR hrtimer_test_t *param = (FAR hrtimer_test_t *)timer; /* Change status if in expected state */ if (param->state == 1) { param->state = 0; param->count++; } /* Verify state wasn't changed by another critical section */ ASSERT(param->state == 0); param->state = 1; return 0; } /**************************************************************************** * Name: hrtimer_test_callback_critdelay * * Description: * Critical section delay test callback. This callback simulates a * long-running timer callback with spinlock protection. It's used to * test timer cancellation behavior with in-progress callbacks. * * Input Parameters: * timer - Pointer to the HRTimer instance * expired_ns - Time when the timer expired (unused) * * Returned Value: * 300 microseconds (reschedules timer for periodic behavior) * ****************************************************************************/ static uint64_t hrtimer_test_callback_critdelay(FAR const hrtimer_t *timer, uint64_t expired_ns) { FAR hrtimer_test_t *param = (FAR hrtimer_test_t *)timer; FAR spinlock_t *lock = ¶m->lock; irqstate_t flags; /* Protect count increment with spinlock */ flags = spin_lock_irqsave(lock); param->count++; spin_unlock_irqrestore(lock, flags); /* Simulate work with 100 microsecond delay */ up_ndelay(100 * NSEC_PER_USEC); /* Return reschedule interval for periodic behavior */ return 300 * NSEC_PER_USEC; } /**************************************************************************** * Name: hrtimer_test_cancel_sync * * Description: * Test HRTimer cancel synchronization. This function tests that * hrtimer_cancel_sync properly waits for callback completion before * returning. It verifies that timer callbacks complete execution even * when cancellation is requested. * * Input Parameters: * param - Test context structure containing timer and state information * ****************************************************************************/ static void hrtimer_test_cancel_sync(FAR hrtimer_test_t *param) { unsigned int idx = 0; ASSERT(!param->timer.func); param->count = 0; for (idx = 0; idx < HRTIMER_TEST_CSECTION; ) { param->state = 0; hrtimer_start(¶m->timer, hrtimer_test_callback_crita, 0, HRTIMER_MODE_REL); hrtimer_cancel_sync(¶m->timer); param->state = 1; hrtimer_start(¶m->timer, hrtimer_test_callback_critb, 0, HRTIMER_MODE_REL); if (++idx % (HRTIMER_TEST_CSECTION / 4) == 0) { printf("hrtimer_test_cancel_sync: passed %u times\n", idx); } hrtimer_cancel_sync(¶m->timer); } } /**************************************************************************** * Name: hrtimer_test_cancel_periodic * * Description: * Test periodic HRTimer cancellation. This function tests that periodic * timers can be properly cancelled and that they don't restart after * cancellation. It verifies that timer cancellation is effective and * persistent. * * Input Parameters: * param - Test context structure containing timer and state information * ****************************************************************************/ static void hrtimer_test_cancel_periodic(FAR hrtimer_test_t *param) { uint64_t count; unsigned int idx = 0; FAR spinlock_t *lock = ¶m->lock; ASSERT(!param->timer.func); param->count = 0; for (idx = 0; idx < HRTIMER_TEST_CSECTION; idx++) { irqstate_t flags = spin_lock_irqsave(lock); hrtimer_start(¶m->timer, hrtimer_test_callback_critdelay, 0, HRTIMER_MODE_REL); spin_unlock_irqrestore(lock, flags); up_ndelay(10000); flags = spin_lock_irqsave(lock); hrtimer_start(¶m->timer, hrtimer_test_callback_critdelay, 0, HRTIMER_MODE_REL); spin_unlock_irqrestore(lock, flags); hrtimer_cancel(¶m->timer); up_ndelay(10000); /* The hrtimer should not restart after cancellation */ ASSERT(!param->timer.func); hrtimer_cancel_sync(¶m->timer); count = param->count; hrtimer_test_ndelay(10000); ASSERT(count == param->count); if (++idx % (HRTIMER_TEST_CSECTION / 4) == 0) { printf("hrtimer_test_cancel_periodic: passed %u times, count = %" PRIu64 "\n", idx, param->count); } } hrtimer_cancel_sync(¶m->timer); } #endif /**************************************************************************** * Name: hrtimer_test_thread * * Description: * HRTimer test thread function. This function runs a comprehensive test * suite for HRTimer functionality. It tests various scenarios including * one-shot timers, periodic timers, random delays, maximum delays, and * cancellation behavior. * * Input Parameters: * arg - Thread argument (unused) * * Returned Value: * NULL. * ****************************************************************************/ static void *hrtimer_test_thread(void *arg) { hrtimer_test_t param = { 0 }; hrtimer_init(¶m.timer); /* Test one-shot timers with various delays */ /* Delay = 0 */ hrtimer_test_oneshot(¶m, 0u); /* 0 < Delay < 10000 */ hrtimer_test_oneshot(¶m, 1u); hrtimer_test_oneshot(¶m, 10u); hrtimer_test_oneshot(¶m, 100u); hrtimer_test_oneshot(¶m, 1000u); hrtimer_test_oneshot(¶m, 10000u); /* 10000 < Delay < 10000000 */ hrtimer_test_oneshot(¶m, 100000u); hrtimer_test_oneshot(¶m, 1000000u); hrtimer_test_oneshot(¶m, 10000000u); #ifdef CONFIG_SMP /* Test SMP-specific cancellation behavior */ hrtimer_test_cancel_sync(¶m); hrtimer_test_cancel_periodic(¶m); #endif /* Test maximum delay handling */ hrtimer_test_maximum(¶m); /* Test periodic timer with 1ms period */ hrtimer_test_period(¶m, 1000000u, 128u); /* Test random delay and cancel scenarios */ hrtimer_test_rand(¶m, 12345u); hrtimer_test_rand_cancel(¶m, 67890u); return 0; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: hrtimer_test * * Description: * Entry point for high-resolution timer functional test. * * - Initializes a HRTimer * - Starts it with a 500ms relative timeout * - Verifies subsequent expirations occur at 500ms intervals * * Input Parameters: * None * * Returned Value: * None * ****************************************************************************/ void hrtimer_test(void) { struct sched_param sparam; unsigned int thread_id; pthread_attr_t attr; pthread_t pthreads[HRTIMER_TEST_THREAD_NR]; printf("hrtimer_test start...\n"); /* Initialize thread attributes */ ASSERT(pthread_attr_init(&attr) == 0); sparam.sched_priority = PTHREAD_DEFAULT_PRIORITY; ASSERT(pthread_attr_setschedparam(&attr, &sparam) == 0); /* Create multiple test threads */ for (thread_id = 0; thread_id < HRTIMER_TEST_THREAD_NR; thread_id++) { ASSERT(pthread_create(&pthreads[thread_id], &attr, hrtimer_test_thread, NULL) == 0); } /* Wait for all threads to complete */ for (thread_id = 0; thread_id < HRTIMER_TEST_THREAD_NR; thread_id++) { pthread_join(pthreads[thread_id], NULL); } /* Clean up thread attributes */ ASSERT(pthread_attr_destroy(&attr) == 0); printf("hrtimer_test end...\n"); }