| /* |
| * Copyright (c) 2012-2015 Wind River Systems, Inc. |
| * |
| * 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. |
| */ |
| |
| /* |
| * @file |
| * @brief Test nanokernel timer APIs |
| * |
| * This module tests the following timer related routines: |
| * nano_timer_init(), nano_fiber_timer_start(), nano_fiber_timer_stop(), |
| * nano_fiber_timer_test(), nano_task_timer_start(), |
| * nano_task_timer_stop(), nano_task_timer_test(), |
| * sys_tick_get_32(), sys_cycle_get_32(), sys_tick_delta() |
| */ |
| |
| #include <tc_util.h> |
| #include <arch/cpu.h> |
| |
| #define TWO_SECONDS (2 * sys_clock_ticks_per_sec) |
| #define SIX_SECONDS (6 * sys_clock_ticks_per_sec) |
| |
| #define SHORT_TIMEOUT (1 * sys_clock_ticks_per_sec) |
| #define LONG_TIMEOUT (5 * sys_clock_ticks_per_sec) |
| #define MID_TIMEOUT (3 * sys_clock_ticks_per_sec) |
| |
| #ifndef FIBER_STACKSIZE |
| #define FIBER_STACKSIZE 2000 |
| #endif |
| #define FIBER_PRIORITY 4 |
| |
| #ifndef FIBER2_STACKSIZE |
| #define FIBER2_STACKSIZE 2000 |
| #endif |
| #define FIBER2_PRIORITY 10 |
| |
| typedef void (*timer_start_func)(struct nano_timer *, int); |
| typedef void (*timer_stop_func)(struct nano_timer *); |
| typedef void* (*timer_test_func)(struct nano_timer *, int32_t); |
| |
| static struct nano_timer timer; |
| static struct nano_timer shortTimer; |
| static struct nano_timer longTimer; |
| static struct nano_timer midTimer; |
| |
| static struct nano_sem wakeTask; |
| static struct nano_sem wakeFiber; |
| |
| static void *timerData[1]; |
| static void *shortTimerData[1]; |
| static void *longTimerData[1]; |
| static void *midTimerData[1]; |
| |
| static int fiberDetectedError = 0; |
| static char __stack fiberStack[FIBER_STACKSIZE]; |
| static char __stack fiber2Stack[FIBER2_STACKSIZE]; |
| |
| /** |
| * |
| * @brief Initialize nanokernel objects |
| * |
| * This routine initializes the nanokernel objects used in the LIFO tests. |
| * |
| * @return N/A |
| */ |
| |
| void initNanoObjects(void) |
| { |
| nano_timer_init(&timer, timerData); |
| nano_timer_init(&shortTimer, shortTimerData); |
| nano_timer_init(&longTimer, longTimerData); |
| nano_timer_init(&midTimer, midTimerData); |
| nano_sem_init(&wakeTask); |
| nano_sem_init(&wakeFiber); |
| } |
| |
| /** |
| * |
| * @brief Basic checking of time spent waiting upon a timer |
| * |
| * This routine can be called from a task or a fiber to wait upon a timer. |
| * It will busy wait until the current tick ends, at which point it will |
| * start and then wait upon a timer. The length of time it spent waiting |
| * gets cross-checked with the sys_tick_get_32() and nanoTimeElapsed() APIs. |
| * All three are expected to match up, but a tolerance of one (1) tick is |
| * considered acceptable. |
| * |
| * This routine can be considered as testing sys_tick_get_32(), |
| * nanoTimeElapsed() and nanoXXXTimerGetW() successful expiration cases. |
| * |
| * @param startRtn routine to start the timer |
| * @param testRtn routine to get and wait for the timer |
| * @param pTimer pointer to the timer |
| * @param pTimerData pointer to the expected timer data |
| * @param ticks number of ticks to wait |
| * |
| * @return TC_PASS on success, TC_FAIL on failure |
| */ |
| |
| int basicTimerWait(timer_start_func startRtn, timer_test_func testRtn, |
| struct nano_timer *pTimer, void *pTimerData, int ticks) |
| { |
| int64_t reftime; /* reference time for tick delta */ |
| uint32_t tick; /* current tick */ |
| uint32_t elapsed_32; /* # of elapsed ticks for 32-bit functions*/ |
| int64_t elapsed; /* # of elapsed ticks */ |
| uint32_t duration; /* duration of the test in ticks */ |
| void *result; /* value returned from timer get routine */ |
| int busywaited = 0; /* non-zero if <testRtn> returns NULL */ |
| |
| TC_PRINT(" - test expected to take four seconds\n"); |
| |
| tick = sys_tick_get_32(); |
| while (sys_tick_get_32() == tick) { |
| /* Align to a tick boundary */ |
| } |
| |
| tick++; |
| (void) sys_tick_delta(&reftime); |
| startRtn(pTimer, ticks); /* Start the timer */ |
| result = testRtn(pTimer, TICKS_UNLIMITED);/* Wait for the timer to expire */ |
| |
| elapsed_32 = sys_tick_delta_32(&reftime); |
| duration = sys_tick_get_32() - tick; |
| |
| /* |
| * The difference between <duration> and <elapsed> is expected to be zero |
| * however, the test is allowing for tolerance of an extra tick in case of |
| * timing variations. |
| */ |
| |
| if ((result != pTimerData) || |
| (duration - elapsed_32 > 1) || ((duration - ticks) > 1)) { |
| return TC_FAIL; |
| } |
| |
| /* Check that the non-wait-timer-get routine works properly. */ |
| tick = sys_tick_get_32(); |
| while (sys_tick_get_32() == tick) { |
| /* Align to a tick boundary */ |
| } |
| |
| tick++; |
| (void) sys_tick_delta(&reftime); |
| startRtn(pTimer, ticks); /* Start the timer */ |
| while ((result = testRtn(pTimer, TICKS_NONE)) == NULL) { |
| busywaited = 1; |
| } |
| elapsed = sys_tick_delta(&reftime); |
| duration = sys_tick_get_32() - tick; |
| |
| if ((busywaited != 1) || (result != pTimerData) || |
| (duration - elapsed > 1) || ((duration - ticks) > 1)) { |
| return TC_FAIL; |
| } |
| |
| return TC_PASS; |
| } |
| |
| /** |
| * |
| * @brief Start four timers |
| * |
| * This routine starts four timers. |
| * The first (<timer>) is added to an empty list of timers. |
| * The second (<longTimer>) is added to the end of the list of timers. |
| * The third (<shortTimer>) is added to the head of the list of timers. |
| * The fourth (<midTimer>) is added to the middle of the list of timers. |
| * |
| * Four timers are used so that the various paths can be tested. |
| * |
| * @param startRtn routine to start the timers |
| * |
| * @return N/A |
| */ |
| |
| void startTimers(timer_start_func startRtn) |
| { |
| int tick; /* current tick */ |
| |
| tick = sys_tick_get_32(); |
| while (sys_tick_get_32() == tick) { |
| /* Wait for the end of the tick */ |
| } |
| |
| startRtn(&timer, TWO_SECONDS); |
| startRtn(&longTimer, LONG_TIMEOUT); |
| startRtn(&shortTimer, SHORT_TIMEOUT); |
| startRtn(&midTimer, MID_TIMEOUT); |
| } |
| |
| /** |
| * |
| * @brief Busy wait while checking timers expire in the correct order |
| * |
| * This routine checks that the four timers created using startTimers() finish |
| * in the correct order. It busy waits on all four timers waiting until they |
| * expire. The timers are expected to expire in the following order: |
| * <shortTimer>, <timer>, <midTimer>, <longTimer> |
| * |
| * @param testRtn timer wait routine (fiber or task) |
| * |
| * @return TC_PASS on success, TC_FAIL on failure |
| */ |
| |
| int busyWaitTimers(timer_test_func testRtn) |
| { |
| int numExpired = 0; /* # of expired timers */ |
| void *result; /* value returned from <testRtn> */ |
| uint32_t ticks; /* tick by which time test should be complete */ |
| |
| TC_PRINT(" - test expected to take five or six seconds\n"); |
| |
| ticks = sys_tick_get_32() + SIX_SECONDS; |
| while ((numExpired != 4) && (sys_tick_get_32() < ticks)) { |
| result = testRtn(&timer, TICKS_NONE); |
| if (result != NULL) { |
| numExpired++; |
| if ((result != timerData) || (numExpired != 2)) { |
| TC_ERROR("Expected <timer> to expire 2nd, not 0x%x\n", |
| result); |
| return TC_FAIL; |
| } |
| } |
| |
| result = testRtn(&shortTimer, TICKS_NONE); |
| if (result != NULL) { |
| numExpired++; |
| if ((result != shortTimerData) || (numExpired != 1)) { |
| TC_ERROR("Expected <shortTimer> to expire 1st, not 0x%x\n", |
| result); |
| return TC_FAIL; |
| } |
| } |
| |
| result = testRtn(&midTimer, TICKS_NONE); |
| if (result != NULL) { |
| numExpired++; |
| if ((result != midTimerData) || (numExpired != 3)) { |
| TC_ERROR("Expected <midTimer> to expire 3rd, not 0x%x\n", |
| result); |
| return TC_FAIL; |
| } |
| } |
| |
| result = testRtn(&longTimer, TICKS_NONE); |
| if (result != NULL) { |
| numExpired++; |
| if ((result != longTimerData) || (numExpired != 4)) { |
| TC_ERROR("Expected <longTimer> to expire 4th, not 0x%x\n", |
| result); |
| return TC_FAIL; |
| } |
| } |
| } |
| |
| return (sys_tick_get_32() < ticks) ? TC_PASS : TC_FAIL; |
| } |
| |
| /** |
| * |
| * @brief Stop the four timers and make sure they did not expire |
| * |
| * This routine stops the four started timers and then checks the timers for |
| * six seconds to make sure that they did not fire. The four timers will be |
| * stopped in the reverse order in which they were started. Doing so will |
| * exercise the code that removes timers from important locations in the list; |
| * these include the middle, the head, the tail, and the last item. |
| * |
| * @param stopRtn routine to stop timer (fiber or task) |
| * @param testRtn timer wait routine (fiber or task) |
| * |
| * @return TC_PASS on success, TC_FAIL on failure |
| */ |
| |
| int stopTimers(timer_stop_func stopRtn, timer_test_func testRtn) |
| { |
| int startTick; /* tick at which test starts */ |
| int endTick; /* tick by which test should be completed */ |
| |
| stopRtn(&midTimer); |
| stopRtn(&shortTimer); |
| stopRtn(&longTimer); |
| stopRtn(&timer); |
| |
| TC_PRINT(" - test expected to take six seconds\n"); |
| |
| startTick = sys_tick_get_32(); |
| while (sys_tick_get_32() == startTick) { |
| } |
| startTick++; |
| endTick = startTick + SIX_SECONDS; |
| |
| while (sys_tick_get_32() < endTick) { |
| if ((testRtn(&timer, TICKS_NONE) != NULL) || |
| (testRtn(&shortTimer, TICKS_NONE) != NULL) || |
| (testRtn(&midTimer, TICKS_NONE) != NULL) || |
| (testRtn(&longTimer, TICKS_NONE) != NULL)) { |
| return TC_FAIL; |
| } |
| } |
| |
| return TC_PASS; |
| } |
| |
| /** |
| * |
| * @brief Entry point for the second fiber |
| * |
| * The second fiber has a lower priority than the first, but is still given |
| * precedence over the task. |
| * |
| * @param arg1 unused |
| * @param arg2 unused |
| * |
| * @return N/A |
| */ |
| |
| static void fiber2Entry(int arg1, int arg2) |
| { |
| ARG_UNUSED(arg1); |
| ARG_UNUSED(arg2); |
| |
| nano_fiber_timer_stop(&timer); |
| } |
| |
| /** |
| * |
| * @brief Entry point for the fiber portion of the timer tests |
| * |
| * NOTE: The fiber portion of the tests have higher priority than the task |
| * portion of the tests. |
| * |
| * @param arg1 unused |
| * @param arg2 unused |
| * |
| * @return N/A |
| */ |
| |
| static void fiberEntry(int arg1, int arg2) |
| { |
| int rv; /* return value from a test */ |
| void *result; /* return value from timer wait routine */ |
| |
| ARG_UNUSED(arg1); |
| ARG_UNUSED(arg2); |
| |
| TC_PRINT("Fiber testing basic timer functionality\n"); |
| |
| rv = basicTimerWait(nano_fiber_timer_start, nano_fiber_timer_test, |
| &timer, timerData, TWO_SECONDS); |
| |
| nano_fiber_sem_give(&wakeTask); |
| if (rv != TC_PASS) { |
| fiberDetectedError = 1; |
| return; |
| } |
| /* Wait forever - let task run */ |
| nano_fiber_sem_take(&wakeFiber, TICKS_UNLIMITED); |
| |
| /* Check that timers expire in the correct order */ |
| TC_PRINT("Fiber testing timers expire in the correct order\n"); |
| startTimers(nano_fiber_timer_start); |
| rv = busyWaitTimers(nano_fiber_timer_test); |
| nano_fiber_sem_give(&wakeTask); |
| if (rv != TC_PASS) { |
| fiberDetectedError = 2; |
| return; |
| } |
| /* Wait forever - let task run */ |
| nano_fiber_sem_take(&wakeFiber, TICKS_UNLIMITED); |
| |
| /* Check that timers can be stopped */ |
| TC_PRINT("Task testing the stopping of timers\n"); |
| startTimers(nano_fiber_timer_start); |
| rv = stopTimers(nano_fiber_timer_stop, nano_fiber_timer_test); |
| nano_fiber_sem_give(&wakeTask); |
| if (rv != TC_PASS) { |
| fiberDetectedError = 3; |
| return; |
| } |
| /* Wait forever - let task run */ |
| nano_fiber_sem_take(&wakeFiber, TICKS_UNLIMITED); |
| |
| /* Fiber to wait on a timer that will be stopped by another fiber */ |
| TC_PRINT("Fiber to stop a timer that has a waiting fiber\n"); |
| fiber_fiber_start(fiber2Stack, FIBER2_STACKSIZE, fiber2Entry, |
| 0, 0, FIBER2_PRIORITY, 0); |
| nano_fiber_timer_start(&timer, TWO_SECONDS); /* Start timer */ |
| result = nano_fiber_timer_test(&timer, TICKS_UNLIMITED); /* Wait on timer */ |
| /* Control switches to newly created fiber #2 before coming back. */ |
| if (result != NULL) { |
| fiberDetectedError = 4; |
| nano_fiber_sem_give(&wakeTask); |
| return; |
| } |
| |
| /* Fiber to wait on timer that will be stopped by the task */ |
| TC_PRINT("Task to stop a timer that has a waiting fiber\n"); |
| nano_fiber_sem_give(&wakeTask); |
| nano_fiber_timer_start(&timer, TWO_SECONDS); |
| result = nano_fiber_timer_test(&timer, TICKS_UNLIMITED); |
| if (result != NULL) { |
| fiberDetectedError = 5; |
| return; |
| } |
| |
| nano_fiber_sem_give(&wakeTask); |
| } |
| |
| /** |
| * |
| * @brief Test the sys_cycle_get_32() API |
| * |
| * @return TC_PASS on success, TC_FAIL on failure |
| */ |
| |
| int sys_cycle_get_32Test(void) |
| { |
| uint32_t timeStamp1; |
| uint32_t timeStamp2; |
| int i; |
| |
| timeStamp2 = sys_cycle_get_32(); |
| for (i = 0; i < 1000000; i++) { |
| timeStamp1 = timeStamp2; |
| timeStamp2 = sys_cycle_get_32(); |
| |
| if (timeStamp2 < timeStamp1) { |
| TC_ERROR("Timestamp value not increasing with successive calls\n"); |
| return TC_FAIL; |
| } |
| } |
| |
| return TC_PASS; |
| } |
| |
| /** |
| * |
| * @brief Entry point to timer tests |
| * |
| * This is the entry point to the timer tests. |
| * |
| * @return N/A |
| */ |
| |
| void main(void) |
| { |
| int rv; /* return value from tests */ |
| |
| TC_START("Test Nanokernel Timer"); |
| |
| initNanoObjects(); |
| |
| TC_PRINT("Task testing basic timer functionality\n"); |
| rv = basicTimerWait(nano_task_timer_start, nano_task_timer_test, |
| &timer, timerData, TWO_SECONDS); |
| if (rv != TC_PASS) { |
| TC_ERROR("Task-level of waiting for timers failed\n"); |
| goto doneTests; |
| } |
| |
| /* Check that timers expire in the correct order */ |
| TC_PRINT("Task testing timers expire in the correct order\n"); |
| startTimers(nano_task_timer_start); |
| rv = busyWaitTimers(nano_task_timer_test); |
| if (rv != TC_PASS) { |
| TC_ERROR("Task-level timer expiration order failed\n"); |
| goto doneTests; |
| } |
| |
| /* Check that timers can be stopped */ |
| TC_PRINT("Task testing the stopping of timers\n"); |
| startTimers(nano_task_timer_start); |
| rv = stopTimers(nano_task_timer_stop, nano_task_timer_test); |
| if (rv != TC_PASS) { |
| TC_ERROR("Task-level stopping of timers test failed\n"); |
| goto doneTests; |
| } |
| |
| /* |
| * Start the fiber. The fiber will be given a higher priority than the |
| * main task. |
| */ |
| |
| task_fiber_start(fiberStack, FIBER_STACKSIZE, fiberEntry, |
| 0, 0, FIBER_PRIORITY, 0); |
| |
| nano_task_sem_take(&wakeTask, TICKS_UNLIMITED); |
| |
| if (fiberDetectedError == 1) { |
| TC_ERROR("Fiber-level of waiting for timers failed\n"); |
| rv = TC_FAIL; |
| goto doneTests; |
| } |
| |
| nano_task_sem_give(&wakeFiber); |
| nano_task_sem_take(&wakeTask, TICKS_UNLIMITED); |
| |
| if (fiberDetectedError == 2) { |
| TC_ERROR("Fiber-level timer expiration order failed\n"); |
| rv = TC_FAIL; |
| goto doneTests; |
| } |
| |
| nano_task_sem_give(&wakeFiber); |
| nano_task_sem_take(&wakeTask, TICKS_UNLIMITED); |
| |
| if (fiberDetectedError == 3) { |
| TC_ERROR("Fiber-level stopping of timers test failed\n"); |
| rv = TC_FAIL; |
| goto doneTests; |
| } |
| |
| nano_task_sem_give(&wakeFiber); |
| nano_task_sem_take(&wakeTask, TICKS_UNLIMITED); |
| if (fiberDetectedError == 4) { |
| TC_ERROR("Fiber stopping a timer waited upon by a fiber failed\n"); |
| rv = TC_FAIL; |
| goto doneTests; |
| } |
| nano_task_timer_stop(&timer); |
| |
| if (fiberDetectedError == 5) { |
| TC_ERROR("Task stopping a timer waited upon by a fiber failed\n"); |
| rv = TC_FAIL; |
| goto doneTests; |
| } |
| |
| nano_task_sem_take(&wakeTask, TICKS_UNLIMITED); |
| |
| #if 0 |
| /* |
| * Due to recent changes in the i8253 file that correct an issue on real |
| * hardware, this test will fail when run under QEMU. On QEMU, the i8253 |
| * timer can at appear to run backwards. This can generate a false |
| * failure detection when this test is run under QEMU as part of the |
| * standard sanity/regression checks. This suggests that the test is not |
| * of high enough quality to be included during the standard sanity/ |
| * regression checks. |
| */ |
| |
| TC_PRINT("Task testing of sys_cycle_get_32()\n"); |
| rv = sys_cycle_get_32Test(); |
| if (rv != TC_PASS) { |
| TC_ERROR("sys_cycle_get_32Test() failed\n"); |
| goto doneTests; |
| } |
| #endif |
| |
| doneTests: |
| TC_END_RESULT(rv); |
| TC_END_REPORT(rv); |
| } |